···11+My Emacs configuration which is, right now, [spacemacs (develop)](https://github.com/syl20bnr/spacemacs/tree/develop).
22+This prohect is used by my [nix-config](https://github.com/kaychaks/nix-config).
+690
spacemacs
···11+;; -*- mode: emacs-lisp -*-
22+;; This file is loaded by Spacemacs at startup.
33+;; It must be stored in your home directory.
44+55+(defun dotspacemacs/layers ()
66+ "Layer configuration:
77+This function should only modify configuration layer settings."
88+ (setq-default
99+ ;; Base distribution to use. This is a layer contained in the directory
1010+ ;; `+distribution'. For now available distributions are `spacemacs-base'
1111+ ;; or `spacemacs'. (default 'spacemacs) dotspacemacs-distribution 'spacemacs
1212+ ;; Lazy installation of layers (i.e. layers are installed only when a file
1313+ ;; with a supported type is opened). Possible values are `all', `unused'
1414+ ;; and `nil'. `unused' will lazy install only unused layers (i.e. layers
1515+ ;; not listed in variable `dotspacemacs-configuration-layers'), `all' will
1616+ ;; lazy install any layer that support lazy installation even the layers
1717+ ;; listed in `dotspacemacs-configuration-layers'. `nil' disable the lazy
1818+ ;; installation feature and you have to explicitly list a layer in the
1919+ ;; variable `dotspacemacs-configuration-layers' to install it.
2020+ ;; (default 'unused) dotspacemacs-enable-lazy-installation 'unused
2121+ ;; If non-nil then Spacemacs will ask for confirmation before installing
2222+ ;; a layer lazily. (default t)
2323+ dotspacemacs-ask-for-lazy-installation
2424+ t
2525+ ;; If non-nil layers with lazy install support are lazy installed.
2626+ ;; List of additional paths where to look for configuration layers.
2727+ ;; Paths must have a trailing slash (i.e. `~/.mycontribs/')
2828+ dotspacemacs-configuration-layer-path
2929+ '("~/.local/share/spacemacs/private/")
3030+ ;; List of configuration layers to load.
3131+ dotspacemacs-configuration-layers
3232+ '(
3333+ osx
3434+ javascript
3535+ html
3636+ helm
3737+ syntax-checking
3838+ (auto-completion (:variables
3939+ auto-completion-enable-snippets-in-popup t
4040+ auto-completion-enable-help-tooltip t))
4141+ (better-defaults :variables
4242+ better-defaults-move-to-beginning-of-code-first t
4343+ better-defaults-move-to-end-of-code-first nil)
4444+ emacs-lisp
4545+ git
4646+ markdown
4747+ treemacs
4848+ (org :variables
4949+ org-enable-github-support nil)
5050+ (shell :variables
5151+ shell-default-height 30
5252+ shell-default-position 'bottom)
5353+ (spell-checking :variables
5454+ spell-checking-enable-by-default nil
5555+ spell-checking-enable-auto-dictionary nil
5656+ enable-flyspell-auto-completion nil)
5757+ python
5858+ yaml
5959+ shell-scripts
6060+ (haskell :variables
6161+ haskell-completion-backend 'dante
6262+ haskell-enable-ghc-mod-support nil
6363+ haskell-enable-hindent t
6464+ haskell-process-type 'ghci)
6565+ pandoc
6666+ (latex :variables
6767+ latex-build-command "LaTeX")
6868+ bibtex
6969+ octave
7070+7171+ ;; all private layers
7272+ custom-lean
7373+ custom-super-save
7474+ custom-nix
7575+ custom-haskell
7676+ custom-config-org
7777+ custom-config-evil
7878+ custom-direnv
7979+8080+ )
8181+ ;; List of additional packages that will be installed without being
8282+ ;; wrapped in a layer. If you need some configuration for these
8383+ ;; packages, then consider creating a layer. You can also put the
8484+ ;; configuration in `dotspacemacs/user-config'.
8585+ ;; To use a local version of a package, use the `:location' property:
8686+ ;; '(your-package :location "~/path/to/your-package/")
8787+ ;; Also include the dependencies as they will not be resolved automatically.
8888+ dotspacemacs-additional-packages
8989+ '(
9090+ ;; direnv
9191+ )
9292+ ;; A list of packages that cannot be updated.
9393+ dotspacemacs-frozen-packages
9494+ '()
9595+ ;; A list of packages that will not be installed and loaded.
9696+ dotspacemacs-excluded-packages
9797+ '()
9898+ ;; Defines the behaviour of Spacemacs when installing packages.
9999+ ;; Possible values are `used-only', `used-but-keep-unused' and `all'.
100100+ ;; `used-only' installs only explicitly used packages and deletes any unused
101101+ ;; packages as well as their unused dependencies. `used-but-keep-unused'
102102+ ;; installs only the used packages but won't delete unused ones. `all'
103103+ ;; installs *all* packages supported by Spacemacs and never uninstalls them.
104104+ ;; (default is `used-only')
105105+ dotspacemacs-install-packages
106106+ 'used-only))
107107+108108+(defun dotspacemacs/init ()
109109+ "Initialization:
110110+This function is called at the very beginning of Spacemacs startup,
111111+before layer configuration.
112112+It should only modify the values of Spacemacs settings."
113113+ ;; This setq-default sexp is an exhaustive list of all the supported
114114+ ;; spacemacs settings.
115115+ (setq-default
116116+ ;; If non-nil ELPA repositories are contacted via HTTPS whenever it's
117117+ ;; possible. Set it to nil if you have no way to use HTTPS in your
118118+ ;; environment, otherwise it is strongly recommended to let it set to t.
119119+ ;; This variable has no effect if Emacs is launched with the parameter
120120+ ;; `--insecure' which forces the value of this variable to nil.
121121+ ;; (default t) dotspacemacs-elpa-https nil
122122+ ;; Maximum allowed time in seconds to contact an ELPA repository.
123123+ ;; (default 5) dotspacemacs-elpa-timeout 5
124124+ ;; Set `gc-cons-threshold' and `gc-cons-percentage' when startup finishes.
125125+ ;; This is an advanced option and should not be changed unless you suspect
126126+ ;; performance issues due to garbage collection operations.
127127+ ;; (default '(100000000 0.1))
128128+ dotspacemacs-gc-cons
129129+ '(100000000 0.1)
130130+ ;; If non-nil then Spacelpa repository is the primary source to install
131131+ ;; a locked version of packages. If nil then Spacemacs will install the
132132+ ;; latest version of packages from MELPA. (default nil)
133133+ dotspacemacs-use-spacelpa
134134+ nil
135135+ ;; If non-nil then verify the signature for downloaded Spacelpa archives.
136136+ ;; (default nil)
137137+ dotspacemacs-verify-spacelpa-archives
138138+ nil
139139+ ;; If non-nil then spacemacs will check for updates at startup
140140+ ;; when the current branch is not `develop'. Note that checking for
141141+ ;; new versions works via git commands, thus it calls GitHub services
142142+ ;; whenever you start Emacs. (default nil)
143143+ dotspacemacs-check-for-update
144144+ nil
145145+ ;; If non-nil, a form that evaluates to a package directory. For example, to
146146+ ;; use different package directories for different Emacs versions, set this
147147+ ;; to `emacs-version'. (default 'emacs-version)
148148+ dotspacemacs-elpa-subdirectory
149149+ 'emacs-version
150150+ ;; One of `vim', `emacs' or `hybrid'.
151151+ ;; `hybrid' is like `vim' except that `insert state' is replaced by the
152152+ ;; `hybrid state' with `emacs' key bindings. The value can also be a list
153153+ ;; with `:variables' keyword (similar to layers). Check the editing styles
154154+ ;; section of the documentation for details on available variables.
155155+ ;; (default 'vim)
156156+ dotspacemacs-editing-style
157157+ 'vim
158158+ ;; If non-nil output loading progress in `*Messages*' buffer. (default nil)
159159+ dotspacemacs-verbose-loading
160160+ t
161161+ ;; Specify the startup banner. Default value is `official', it displays
162162+ ;; the official spacemacs logo. An integer value is the index of text
163163+ ;; banner, `random' chooses a random text banner in `core/banners'
164164+ ;; directory. A string value must be a path to an image format supported
165165+ ;; by your Emacs build.
166166+ ;; If the value is nil then no banner is displayed. (default 'official)
167167+ dotspacemacs-startup-banner
168168+ nil
169169+ ;; List of items to show in startup buffer or an association list of
170170+ ;; the form `(list-type . list-size)`. If nil then it is disabled.
171171+ ;; Possible values for list-type are:
172172+ ;; `recents' `bookmarks' `projects' `agenda' `todos'.
173173+ ;; List sizes may be nil, in which case
174174+ ;; `spacemacs-buffer-startup-lists-length' takes effect.
175175+ dotspacemacs-startup-lists
176176+ nil
177177+ ;; True if the home buffer should respond to resize events. (default t)
178178+ dotspacemacs-startup-buffer-responsive
179179+ t
180180+ ;; Default major mode of the scratch buffer (default `text-mode')
181181+ dotspacemacs-scratch-mode
182182+ 'org-mode
183183+ ;; Initial message in the scratch buffer, such as "Welcome to Spacemacs!"
184184+ ;; (default nil)
185185+ dotspacemacs-initial-scratch-message
186186+ nil
187187+ ;; List of themes, the first of the list is loaded when spacemacs starts.
188188+ ;; Press `SPC T n' to cycle to the next theme in the list (works great
189189+ ;; with 2 themes variants, one dark and one light)
190190+ dotspacemacs-themes
191191+ '(spacemacs-dark
192192+ sanityinc-solarized-dark
193193+ spacemacs-light
194194+ material
195195+ )
196196+ ;; Set the theme for the Spaceline. Supported themes are `spacemacs',
197197+ ;; `all-the-icons', `custom', `vim-powerline' and `vanilla'. The first three
198198+ ;; are spaceline themes. `vanilla' is default Emacs mode-line. `custom' is a
199199+ ;; user defined themes, refer to the DOCUMENTATION.org for more info on how
200200+ ;; to create your own spaceline theme. Value can be a symbol or list with\
201201+ ;; additional properties.
202202+ ;; (default '(spacemacs :separator wave :separator-scale 1.5))
203203+ dotspacemacs-mode-line-theme
204204+ '(vim-powerline :separator wave
205205+ :separator-scale 1.5)
206206+ ;; If non-nil the cursor color matches the state color in GUI Emacs.
207207+ ;; (default t)
208208+ dotspacemacs-colorize-cursor-according-to-state
209209+ t
210210+ ;; Default font, or prioritized list of fonts. `powerline-scale' allows to
211211+ ;; quickly tweak the mode-line size to make separators look not too crappy.
212212+ dotspacemacs-default-font
213213+ '("Fira Code" :size 16
214214+ :weight normal
215215+ :width normal
216216+ :powerline-scale 1.1)
217217+ ;; The leader key (default "SPC")
218218+ dotspacemacs-leader-key
219219+ "SPC"
220220+ ;; The key used for Emacs commands `M-x' (after pressing on the leader key).
221221+ ;; (default "SPC")
222222+ dotspacemacs-emacs-command-key
223223+ "SPC"
224224+ ;; The key used for Vim Ex commands (default ":")
225225+ dotspacemacs-ex-command-key
226226+ ":"
227227+ ;; The leader key accessible in `emacs state' and `insert state'
228228+ ;; (default "M-m")
229229+ dotspacemacs-emacs-leader-key
230230+ "M-m"
231231+ ;; Major mode leader key is a shortcut key which is the equivalent of
232232+ ;; pressing `<leader> m`. Set it to `nil` to disable it. (default ",")
233233+ dotspacemacs-major-mode-leader-key
234234+ ","
235235+ ;; Major mode leader key accessible in `emacs state' and `insert state'.
236236+ ;; (default "C-M-m")
237237+ dotspacemacs-major-mode-emacs-leader-key
238238+ "C-M-m"
239239+ ;; These variables control whether separate commands are bound in the GUI to
240240+ ;; the key pairs `C-i', `TAB' and `C-m', `RET'.
241241+ ;; Setting it to a non-nil value, allows for separate commands under `C-i'
242242+ ;; and TAB or `C-m' and `RET'.
243243+ ;; In the terminal, these pairs are generally indistinguishable, so this only
244244+ ;; works in the GUI. (default nil)
245245+ dotspacemacs-distinguish-gui-tab
246246+ nil
247247+ ;; If non-nil `Y' is remapped to `y$' in Evil states. (default nil)
248248+ dotspacemacs-remap-Y-to-y$
249249+ nil
250250+ ;; If non-nil, the shift mappings `<' and `>' retain visual state if used
251251+ ;; there. (default t)
252252+ dotspacemacs-retain-visual-state-on-shift
253253+ t
254254+ ;; If non-nil, `J' and `K' move lines up and down when in visual mode.
255255+ ;; (default nil)
256256+ dotspacemacs-visual-line-move-text
257257+ t
258258+ ;; If non-nil, inverse the meaning of `g' in `:substitute' Evil ex-command.
259259+ ;; (default nil)
260260+ dotspacemacs-ex-substitute-global
261261+ nil
262262+ ;; Name of the default layout (default "Default")
263263+ dotspacemacs-default-layout-name
264264+ "Default"
265265+ ;; If non-nil the default layout name is displayed in the mode-line.
266266+ ;; (default nil)
267267+ dotspacemacs-display-default-layout
268268+ nil
269269+ ;; If non-nil then the last auto saved layouts are resumed automatically upon
270270+ ;; start. (default nil)
271271+ dotspacemacs-auto-resume-layouts
272272+ nil
273273+ ;; If non-nil, auto-generate layout name when creating new layouts. Only has
274274+ ;; effect when using the "jump to layout by number" commands. (default nil)
275275+ dotspacemacs-auto-generate-layout-names
276276+ nil
277277+ ;; Size (in MB) above which spacemacs will prompt to open the large file
278278+ ;; literally to avoid performance issues. Opening a file literally means that
279279+ ;; no major mode or minor modes are active. (default is 1)
280280+ dotspacemacs-large-file-size
281281+ 1
282282+ ;; Location where to auto-save files. Possible values are `original' to
283283+ ;; auto-save the file in-place, `cache' to auto-save the file to another
284284+ ;; file stored in the cache directory and `nil' to disable auto-saving.
285285+ ;; (default 'cache)
286286+ dotspacemacs-auto-save-file-location
287287+ nil
288288+ ;; Maximum number of rollback slots to keep in the cache. (default 5)
289289+ dotspacemacs-max-rollback-slots
290290+ 5
291291+ ;; If non-nil, `helm' will try to minimize the space it uses. (default nil)
292292+ dotspacemacs-helm-resize
293293+ nil
294294+ ;; if non-nil, the helm header is hidden when there is only one source.
295295+ ;; (default nil)
296296+ dotspacemacs-helm-no-header
297297+ t
298298+ ;; define the position to display `helm', options are `bottom', `top',
299299+ ;; `left', or `right'. (default 'bottom)
300300+ dotspacemacs-helm-position
301301+ 'bottom
302302+ ;; Controls fuzzy matching in helm. If set to `always', force fuzzy matching
303303+ ;; in all non-asynchronous sources. If set to `source', preserve individual
304304+ ;; source settings. Else, disable fuzzy matching in all sources.
305305+ ;; (default 'always)
306306+ dotspacemacs-helm-use-fuzzy
307307+ 'always
308308+ ;; If non-nil, the paste transient-state is enabled. While enabled, pressing
309309+ ;; `p' several times cycles through the elements in the `kill-ring'.
310310+ ;; (default nil)
311311+ dotspacemacs-enable-paste-transient-state
312312+ nil
313313+ ;; Which-key delay in seconds. The which-key buffer is the popup listing
314314+ ;; the commands bound to the current keystroke sequence. (default 0.4)
315315+ dotspacemacs-which-key-delay
316316+ 0.4
317317+ ;; Which-key frame position. Possible values are `right', `bottom' and
318318+ ;; `right-then-bottom'. right-then-bottom tries to display the frame to the
319319+ ;; right; if there is insufficient space it displays it at the bottom.
320320+ ;; (default 'bottom)
321321+ dotspacemacs-which-key-position
322322+ 'bottom
323323+ ;; Control where `switch-to-buffer' displays the buffer. If nil,
324324+ ;; `switch-to-buffer' displays the buffer in the current window even if
325325+ ;; another same-purpose window is available. If non-nil, `switch-to-buffer'
326326+ ;; displays the buffer in a same-purpose window even if the buffer can be
327327+ ;; displayed in the current window. (default nil)
328328+ dotspacemacs-switch-to-buffer-prefers-purpose
329329+ nil
330330+ ;; If non-nil a progress bar is displayed when spacemacs is loading. This
331331+ ;; may increase the boot time on some systems and emacs builds, set it to
332332+ ;; nil to boost the loading time. (default t)
333333+ dotspacemacs-loading-progress-bar
334334+ nil
335335+ ;; If non-nil the frame is fullscreen when Emacs starts up. (default nil)
336336+ ;; (Emacs 24.4+ only)
337337+ dotspacemacs-fullscreen-at-startup
338338+ nil
339339+ ;; If non-nil `spacemacs/toggle-fullscreen' will not use native fullscreen.
340340+ ;; Use to disable fullscreen animations in OSX. (default nil)
341341+ dotspacemacs-fullscreen-use-non-native
342342+ nil
343343+ ;; If non-nil the frame is maximized when Emacs starts up.
344344+ ;; Takes effect only if `dotspacemacs-fullscreen-at-startup' is nil.
345345+ ;; (default nil) (Emacs 24.4+ only)
346346+ dotspacemacs-maximized-at-startup
347347+ t
348348+ ;; A value from the range (0..100), in increasing opacity, which describes
349349+ ;; the transparency level of a frame when it's active or selected.
350350+ ;; Transparency can be toggled through `toggle-transparency'. (default 90)
351351+ dotspacemacs-active-transparency
352352+ 90
353353+ ;; A value from the range (0..100), in increasing opacity, which describes
354354+ ;; the transparency level of a frame when it's inactive or deselected.
355355+ ;; Transparency can be toggled through `toggle-transparency'. (default 90)
356356+ dotspacemacs-inactive-transparency
357357+ 90
358358+ ;; If non-nil show the titles of transient states. (default t)
359359+ dotspacemacs-show-transient-state-title
360360+ t
361361+ ;; If non-nil show the color guide hint for transient state keys. (default t)
362362+ dotspacemacs-show-transient-state-color-guide
363363+ t
364364+ ;; If non-nil unicode symbols are displayed in the mode line. (default t)
365365+ dotspacemacs-mode-line-unicode-symbols
366366+ t
367367+ ;; If non-nil smooth scrolling (native-scrolling) is enabled. Smooth
368368+ ;; scrolling overrides the default behavior of Emacs which recenters point
369369+ ;; when it reaches the top or bottom of the screen. (default t)
370370+ dotspacemacs-smooth-scrolling
371371+ t
372372+ ;; Control line numbers activation.
373373+ ;; If set to `t' or `relative' line numbers are turned on in all `prog-mode' and
374374+ ;; `text-mode' derivatives. If set to `relative', line numbers are relative.
375375+ ;; This variable can also be set to a property list for finer control:
376376+ ;; '(:relative nil
377377+ ;; :disabled-for-modes dired-mode
378378+ ;; doc-view-mode
379379+ ;; markdown-mode
380380+ ;; org-mode
381381+ ;; pdf-view-mode
382382+ ;; text-mode
383383+ ;; :size-limit-kb 1000)
384384+ ;; (default nil)
385385+ dotspacemacs-line-numbers
386386+ 't
387387+ ;; Code folding method. Possible values are `evil' and `origami'.
388388+ ;; (default 'evil)
389389+ dotspacemacs-folding-method
390390+ 'evil
391391+ ;; If non-nil `smartparens-strict-mode' will be enabled in programming modes.
392392+ ;; (default nil)
393393+ dotspacemacs-smartparens-strict-mode
394394+ nil
395395+ ;; If non-nil pressing the closing parenthesis `)' key in insert mode passes
396396+ ;; over any automatically added closing parenthesis, bracket, quote, etc…
397397+ ;; This can be temporary disabled by pressing `C-q' before `)'. (default nil)
398398+ dotspacemacs-smart-closing-parenthesis
399399+ nil
400400+ ;; Select a scope to highlight delimiters. Possible values are `any',
401401+ ;; `current', `all' or `nil'. Default is `all' (highlight any scope and
402402+ ;; emphasis the current one). (default 'all)
403403+ dotspacemacs-highlight-delimiters
404404+ 'all
405405+ ;; If non-nil, start an Emacs server if one is not already running.
406406+ dotspacemacs-enable-server
407407+ nil
408408+ ;; If non-nil, advise quit functions to keep server open when quitting.
409409+ ;; (default nil)
410410+ dotspacemacs-persistent-server
411411+ nil
412412+ ;; List of search tool executable names. Spacemacs uses the first installed
413413+ ;; tool of the list. Supported tools are `rg', `ag', `pt', `ack' and `grep'.
414414+ ;; (default '("rg" "ag" "pt" "ack" "grep"))
415415+ dotspacemacs-search-tools
416416+ '("rg" "ag" "pt" "ack" "grep")
417417+ ;; Format specification for setting the frame title.
418418+ ;; %a - the `abbreviated-file-name', or `buffer-name'
419419+ ;; %t - `projectile-project-name'
420420+ ;; %I - `invocation-name'
421421+ ;; %S - `system-name'
422422+ ;; %U - contents of $USER
423423+ ;; %b - buffer name
424424+ ;; %f - visited file name
425425+ ;; %F - frame name
426426+ ;; %s - process status
427427+ ;; %p - percent of buffer above top of window, or Top, Bot or All
428428+ ;; %P - percent of buffer above bottom of window, perhaps plus Top, or Bot or All
429429+ ;; %m - mode name
430430+ ;; %n - Narrow if appropriate
431431+ ;; %z - mnemonics of buffer, terminal, and keyboard coding systems
432432+ ;; %Z - like %z, but including the end-of-line format
433433+ ;; (default "%I@%S")
434434+ dotspacemacs-frame-title-format
435435+ "%I@%S"
436436+ ;; Format specification for setting the icon title format
437437+ ;; (default nil - same as frame-title-format)
438438+ dotspacemacs-icon-title-format
439439+ nil
440440+ ;; Delete whitespace while saving buffer. Possible values are `all'
441441+ ;; to aggressively delete empty line and long sequences of whitespace,
442442+ ;; `trailing' to delete only the whitespace at end of lines, `changed' to
443443+ ;; delete only whitespace for changed lines or `nil' to disable cleanup.
444444+ ;; (default nil)
445445+ dotspacemacs-whitespace-cleanup
446446+ nil
447447+ ;; Either nil or a number of seconds. If non-nil zone out after the specified
448448+ ;; number of seconds. (default nil)
449449+ dotspacemacs-zone-out-when-idle
450450+ nil
451451+ ;; Run `spacemacs/prettify-org-buffer' when
452452+ ;; visiting README.org files of Spacemacs.
453453+ ;; (default nil)
454454+ dotspacemacs-pretty-docs
455455+ nil))
456456+457457+(defun dotspacemacs/user-init ()
458458+ "Initialization for user code:
459459+This function is called immediately after `dotspacemacs/init', before layer
460460+configuration.
461461+It is mostly for variables that should be set before packages are loaded.
462462+If you are unsure, try setting them in `dotspacemacs/user-config' first."
463463+ ;; (load "/Users/kaushik/Developer/src/personal/git-wip/emacs/git-wip.el")
464464+ (setq exec-path-from-shell-arguments '("-c"))
465465+ )
466466+467467+(defun dotspacemacs/user-config ()
468468+ "Configuration for user code:
469469+This function is called at the very end of Spacemacs startup, after layer
470470+configuration.
471471+Put your configuration code here, except for variables that should be set
472472+before packages are loaded."
473473+ (setq ispell-program-name "/Users/kaushik/.nix-profile/bin/aspell")
474474+475475+ ;; (when (configuration-layer/package-usedp 'haskell)
476476+ ;; (add-hook 'haskell-interactive-mode-hook
477477+ ;; (lambda ()
478478+ ;; (setq-local evil-move-cursor-back nil)))
479479+ ;; (add-hook 'doc-view-mode-hook 'auto-revert-mode)
480480+ ;; (add-hook 'dante-mode-hook
481481+ ;; '(lambda ()
482482+ ;; (flycheck-add-next-checker 'haskell-dante
483483+ ;; '(warning . haskell-hlint)))))
484484+485485+ ;; (spacemacs/set-leader-keys-for-major-mode
486486+ ;; 'haskell-mode "M-." 'xref-find-definitions)
487487+488488+ ;; (require 'beeminder)
489489+490490+ ;; (direnv-mode)
491491+ (global-company-mode)
492492+ (global-set-key (kbd "S-SPC")
493493+ #'company-complete)
494494+495495+ ;; (setq org-ref-pdf-directory '("/Users/kaushik/Documents/Papers Virtual Library/All Papers")
496496+ ;; org-ref-default-bibliography
497497+ ;; '("/Users/kaushik/Documents/Papers Virtual Library/All Papers/Ω Export/All Papers.bib")
498498+ ;; org-ref-bibliography-notes
499499+ ;; (concat org-directory "biblio-notes.org"))
500500+501501+ ;; Fira code
502502+ ;; This works when using emacs --daemon + emacsclient
503503+ (add-hook 'after-make-frame-functions
504504+ (lambda (frame)
505505+ (set-fontset-font t
506506+ '(#Xe100 . #Xe16f)
507507+ "Fira Code Symbol")))
508508+ ;; This works when using emacs without server/client
509509+ (set-fontset-font t
510510+ '(#Xe100 . #Xe16f)
511511+ "Fira Code Symbol")
512512+ ;; I haven't found one statement that makes both of the above situations work, so I use both for now
513513+ (defconst fira-code-font-lock-keywords-alist (mapcar (lambda (regex-char-pair)
514514+ `(,(car regex-char-pair)
515515+ (0
516516+ (prog1 ()
517517+ (compose-region (match-beginning 1)
518518+ (match-end 1)
519519+ ;; The first argument to concat is a string containing a literal tab
520520+521521+ ,(concat " "
522522+ (list (decode-char 'ucs
523523+ (cadr regex-char-pair)))))))))
524524+ '(("\\(www\\)" #Xe100)
525525+ ("[^/]\\(\\*\\*\\)[^/]" #Xe101)
526526+ ("\\(\\*\\*\\*\\)" #Xe102)
527527+ ("\\(\\*\\*/\\)" #Xe103)
528528+ ("\\(\\*>\\)" #Xe104)
529529+ ("[^*]\\(\\*/\\)" #Xe105)
530530+ ("\\(\\\\\\\\\\)" #Xe106)
531531+ ("\\(\\\\\\\\\\\\\\)" #Xe107)
532532+ ("\\({-\\)" #Xe108)
533533+ ;; ("\\(\\[\\]\\)" #Xe109) This is the [] ligature and I don't like
534534+ ("\\(::\\)" #Xe10a)
535535+ ("\\(:::\\)" #Xe10b)
536536+ ("[^=]\\(:=\\)" #Xe10c)
537537+ ("\\(!!\\)" #Xe10d)
538538+ ("\\(!=\\)" #Xe10e)
539539+ ("\\(!==\\)" #Xe10f)
540540+ ("\\(-}\\)" #Xe110)
541541+ ("\\(--\\)" #Xe111)
542542+ ("\\(---\\)" #Xe112)
543543+ ("\\(-->\\)" #Xe113)
544544+ ("[^-]\\(->\\)" #Xe114)
545545+ ("\\(->>\\)" #Xe115)
546546+ ("\\(-<\\)" #Xe116)
547547+ ("\\(-<<\\)" #Xe117)
548548+ ("\\(-~\\)" #Xe118)
549549+ ("\\(#{\\)" #Xe119)
550550+ ("\\(#\\[\\)" #Xe11a)
551551+ ("\\(##\\)" #Xe11b)
552552+ ("\\(###\\)" #Xe11c)
553553+ ("\\(####\\)" #Xe11d)
554554+ ("\\(#(\\)" #Xe11e)
555555+ ("\\(#\\?\\)" #Xe11f)
556556+ ("\\(#_\\)" #Xe120)
557557+ ("\\(#_(\\)" #Xe121)
558558+ ("\\(\\.-\\)" #Xe122)
559559+ ("\\(\\.=\\)" #Xe123)
560560+ ("\\(\\.\\.\\)" #Xe124)
561561+ ("\\(\\.\\.<\\)" #Xe125)
562562+ ("\\(\\.\\.\\.\\)" #Xe126)
563563+ ("\\(\\?=\\)" #Xe127)
564564+ ("\\(\\?\\?\\)" #Xe128)
565565+ ("\\(;;\\)" #Xe129)
566566+ ("\\(/\\*\\)" #Xe12a)
567567+ ("\\(/\\*\\*\\)" #Xe12b)
568568+ ("\\(/=\\)" #Xe12c)
569569+ ("\\(/==\\)" #Xe12d)
570570+ ("\\(/>\\)" #Xe12e)
571571+ ("\\(//\\)" #Xe12f)
572572+ ("\\(///\\)" #Xe130)
573573+ ("\\(&&\\)" #Xe131)
574574+ ("\\(||\\)" #Xe132)
575575+ ("\\(||=\\)" #Xe133)
576576+ ;("[^|]\\(|=\\)" #Xe134)
577577+ ("\\(|>\\)" #Xe135)
578578+ ("\\(\\^=\\)" #Xe136)
579579+ ("\\(\\$>\\)" #Xe137)
580580+ ("\\(\\+\\+\\)" #Xe138)
581581+ ("\\(\\+\\+\\+\\)" #Xe139)
582582+ ("\\(\\+>\\)" #Xe13a)
583583+ ("\\(=:=\\)" #Xe13b)
584584+ ;("[^!/]\\(==\\)[^>]" #Xe13c)
585585+ ("\\(===\\)" #Xe13d)
586586+ ("\\(==>\\)" #Xe13e)
587587+ ;("[^=]\\(=>\\)" #Xe13f)
588588+ ("\\(=>>\\)" #Xe140)
589589+ ("\\(<=\\)" #Xe141)
590590+ ("\\(=<<\\)" #Xe142)
591591+ ("\\(=/=\\)" #Xe143)
592592+ ("\\(>-\\)" #Xe144)
593593+ ("\\(>=\\)" #Xe145)
594594+ ("\\(>=>\\)" #Xe146)
595595+ ("[^-=]\\(>>\\)" #Xe147)
596596+ ("\\(>>-\\)" #Xe148)
597597+ ("\\(>>=\\)" #Xe149)
598598+ ("\\(>>>\\)" #Xe14a)
599599+ ("\\(<\\*\\)" #Xe14b)
600600+ ("\\(<\\*>\\)" #Xe14c)
601601+ ("\\(<|\\)" #Xe14d)
602602+ ("\\(<|>\\)" #Xe14e)
603603+ ("\\(<\\$\\)" #Xe14f)
604604+ ("\\(<\\$>\\)" #Xe150)
605605+ ("\\(<!--\\)" #Xe151)
606606+ ("\\(<-\\)" #Xe152)
607607+ ("\\(<--\\)" #Xe153)
608608+ ("\\(<->\\)" #Xe154)
609609+ ("\\(<\\+\\)" #Xe155)
610610+ ("\\(<\\+>\\)" #Xe156)
611611+ ("\\(<=\\)" #Xe157)
612612+ ("\\(<==\\)" #Xe158)
613613+ ("\\(<=>\\)" #Xe159)
614614+ ("\\(<=<\\)" #Xe15a)
615615+ ("\\(<>\\)" #Xe15b)
616616+ ("[^-=]\\(<<\\)" #Xe15c)
617617+ ("\\(<<-\\)" #Xe15d)
618618+ ("\\(<<=\\)" #Xe15e)
619619+ ("\\(<<<\\)" #Xe15f)
620620+ ("\\(<~\\)" #Xe160)
621621+ ("\\(<~~\\)" #Xe161)
622622+ ("\\(</\\)" #Xe162)
623623+ ("\\(</>\\)" #Xe163)
624624+ ("\\(~@\\)" #Xe164)
625625+ ("\\(~-\\)" #Xe165)
626626+ ("\\(~=\\)" #Xe166)
627627+ ("\\(~>\\)" #Xe167)
628628+ ("[^<]\\(~~\\)" #Xe168)
629629+ ("\\(~~>\\)" #Xe169)
630630+ ("\\(%%\\)" #Xe16a)
631631+ ;; ("\\(x\\)" #Xe16b) This ended up being hard to do properly so i'm leaving it out.
632632+ ("[^:=]\\(:\\)[^:=]" #Xe16c)
633633+ ("[^\\+<>]\\(\\+\\)[^\\+<>]" #Xe16d)
634634+ ("[^\\*/<>]\\(\\*\\)[^\\*/<>]" #Xe16f))))
635635+ (defun add-fira-code-symbol-keywords ()
636636+ (font-lock-add-keywords nil fira-code-font-lock-keywords-alist))
637637+ (add-hook 'prog-mode-hook #'add-fira-code-symbol-keywords)
638638+639639+ ;; (defun export-yearly-agenda-as-ics ()
640640+ ;; (interactive)
641641+ ;; (progn (org-agenda-list nil nil 'year) (org-agenda-write "/Users/kaushik/Dropbox/agenda.ics")))
642642+ ;; (autoload 'run-prolog "prolog" "Start a Prolog sub-process." t)
643643+ ;; (autoload 'prolog-mode "prolog" "Major mode for editing Prolog programs." t)
644644+ ;; (setq prolog-system 'swi)
645645+ ;; (setq auto-mode-alist (append '(("\\.pl$" . prolog-mode)
646646+ ;; ("\\.m$" . mercury-mode))
647647+ ;; auto-mode-alist))
648648+649649+ )
650650+651651+652652+653653+;; Do not write anything past this comment. This is where Emacs will
654654+;; auto-generate custom variable definitions.
655655+(defun dotspacemacs/emacs-custom-settings ()
656656+ "Emacs custom settings.
657657+This is an auto-generated function, do not modify its content directly, use
658658+Emacs customize menu instead.
659659+This function is called at the very end of Spacemacs initialization."
660660+(custom-set-variables
661661+ ;; custom-set-variables was added by Custom.
662662+ ;; If you edit it by hand, you could mess it up, so be careful.
663663+ ;; Your init file should contain only one such instance.
664664+ ;; If there is more than one, they won't work right.
665665+ '(dante-load-flags
666666+ (quote
667667+ ("+c" "-Wall" "-ferror-spans" "-fdefer-typed-holes" "-fdefer-type-errors" "-fno-diagnostics-show-caret")))
668668+ '(dante-methods
669669+ (quote
670670+ (new-build new-impure-nix new-nix nix impure-nix nix-ghci stack mafia bare-cabal bare-ghci styx)))
671671+ '(dante-tap-type-time 2)
672672+ '(evil-want-Y-yank-to-eol nil)
673673+ '(haskell-compile-cabal-build-alt-command
674674+ "cabal new-clean -s && cabal new-build --ghc-option=-ferror-spans")
675675+ '(haskell-compile-cabal-build-command "cabal new-build --ghc-option=-ferror-spans")
676676+ '(haskell-hasktags-arguments (quote ("-R")))
677677+ '(haskell-hasktags-path "fast-tags")
678678+ '(haskell-hoogle-command "nil")
679679+ '(haskell-process-suggest-hoogle-imports nil)
680680+ '(haskell-stylish-on-save t)
681681+ '(package-selected-packages
682682+ (quote
683683+ (direnv reveal-in-osx-finder osx-trash osx-dictionary osx-clipboard launchctl treemacs-projectile treemacs-evil treemacs org-download org-brain nix-mode hlint-refactor eyebrowse evil-visual-mark-mode evil-nerd-commenter evil-magit eval-sexp-fu editorconfig doom-modeline eldoc-eval define-word dante cython-mode aggressive-indent ace-link anaconda-mode ivy smartparens goto-chg company window-purpose imenu-list helm helm-core flycheck avy projectile magit f simple-httpd powerline visual-fill-column yasnippet-snippets yapfify yaml-mode xterm-color ws-butler writeroom-mode winum which-key web-mode web-beautify volatile-highlights vi-tilde-fringe uuidgen use-package unfill toc-org tagedit symon super-save string-inflection spaceline-all-the-icons smeargle slim-mode shrink-path shell-pop scss-mode sass-mode restart-emacs rainbow-delimiters pyvenv pytest pyenv-mode py-isort pug-mode prettier-js popwin pippel pipenv pip-requirements pfuture persp-mode pcre2el password-generator paradox pandoc-mode ox-pandoc overseer orgit org-projectile org-present org-pomodoro org-mime org-bullets open-junk-file nameless mwim multi-term move-text mmm-mode material-theme markdown-toc magit-svn magit-gitflow macrostep lorem-ipsum livid-mode live-py-mode link-hint lcr json-navigator json-mode js2-refactor js-doc insert-shebang indent-guide importmagic impatient-mode hungry-delete hl-todo hindent highlight-parentheses highlight-numbers highlight-indentation highlight helm-xref helm-themes helm-swoop helm-pydoc helm-purpose helm-projectile helm-org-rifle helm-mode-manager helm-make helm-lean helm-hoogle helm-gitignore helm-git-grep helm-flx helm-descbinds helm-css-scss helm-company helm-c-yasnippet helm-ag haskell-snippets haskell-mode google-translate golden-ratio gnuplot gitignore-templates gitconfig-mode gitattributes-mode git-timemachine git-messenger git-link git-commit gh-md fuzzy font-lock+ flyspell-correct-helm flycheck-bashate flx-ido fish-mode fill-column-indicator fancy-battery expand-region evil-visualstar evil-unimpaired evil-tutor evil-surround evil-org evil-numbers evil-matchit evil-lisp-state evil-lion evil-indent-plus evil-iedit-state evil-goggles evil-exchange evil-escape evil-ediff evil-cleverparens evil-args evil-anzu eshell-z eshell-prompt-extras esh-help emmet-mode elisp-slime-nav dumb-jump dotenv-mode diminish counsel-projectile company-web company-tern company-statistics company-shell company-lean company-cabal company-auctex company-anaconda column-enforce-mode color-theme-sanityinc-solarized cmm-mode clean-aindent-mode centered-cursor-mode auto-yasnippet auto-highlight-symbol auto-dictionary auto-compile ace-window ace-jump-helm-line ac-ispell))))
684684+(custom-set-faces
685685+ ;; custom-set-faces was added by Custom.
686686+ ;; If you edit it by hand, you could mess it up, so be careful.
687687+ ;; Your init file should contain only one such instance.
688688+ ;; If there is more than one, they won't work right.
689689+ )
690690+)
+21
spacemacs-private/README.md
···11+# Private directory
22+33+The content of this directory is ignored by Git. This is the default place
44+where to store your private configuration layers.
55+66+To create a new configuration layer:
77+88+ SPC SPC configuration-layer/create-layer RET
99+1010+Then enter the name of your configuration in the prompt.
1111+1212+A directory named after the created configuration layer will be created here
1313+along with template files within it (packages.el and extensions.el, more info
1414+on the meaning of those files can be found in the [documentation][conf_layers]).
1515+1616+Each created file has further guidance written in them.
1717+1818+Once the configuration is done, restart Emacs to load, install and configure
1919+your layer.
2020+2121+[conf_layers]: https://github.com/syl20bnr/spacemacs/blob/master/doc/DOCUMENTATION.org#extensions-and-packages
+41
spacemacs-private/custom-config-evil/packages.el
···11+;;; packages.el --- custom-config-evil layer packages file for Spacemacs.
22+;;
33+;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44+;;
55+;; Author: Kaushik Chakraborty <kaushik@AMB00472.local>
66+;; URL: https://github.com/syl20bnr/spacemacs
77+;;
88+;; This file is not part of GNU Emacs.
99+;;
1010+;;; License: GPLv3
1111+1212+;;; Commentary:
1313+1414+;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515+;; a new layer:
1616+;;
1717+;; SPC h SPC layers RET
1818+;;
1919+;;
2020+;; Briefly, each package to be installed or configured by this layer should be
2121+;; added to `custom-config-evil-packages'. Then, for each package PACKAGE:
2222+;;
2323+;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424+;; function `custom-config-evil/init-PACKAGE' to load and initialize the package.
2525+2626+;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727+;; define the functions `custom-config-evil/pre-init-PACKAGE' and/or
2828+;; `custom-config-evil/post-init-PACKAGE' to customize the package as it is loaded.
2929+3030+;;; Code:
3131+3232+(defconst custom-config-evil-packages
3333+ '(evil)
3434+ )
3535+3636+(defun custom-config-evil/post-init-evil ()
3737+ (setq evil-search-module 'evil-search)
3838+ )
3939+4040+4141+;;; packages.el ends here
+142
spacemacs-private/custom-config-org/packages.el
···11+;;; packages.el --- custom-config-org layer packages file for Spacemacs.
22+;;
33+;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44+;;
55+;; Author: Kaushik Chakraborty <kaushik@AMB00472>
66+;; URL: https://github.com/syl20bnr/spacemacs
77+;;
88+;; This file is not part of GNU Emacs.
99+;;
1010+;;; License: GPLv3
1111+1212+;;; Commentary:
1313+1414+;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515+;; a new layer:
1616+;;
1717+;; SPC h SPC layers RET
1818+;;
1919+;;
2020+;; Briefly, each package to be installed or configured by this layer should be
2121+;; added to `custom-config-org-packages'. Then, for each package PACKAGE:
2222+;;
2323+;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424+;; function `custom-config-org/init-PACKAGE' to load and initialize the package.
2525+2626+;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727+;; define the functions `custom-config-org/pre-init-PACKAGE' and/or
2828+;; `custom-config-org/post-init-PACKAGE' to customize the package as it is loaded.
2929+3030+;;; Code:
3131+3232+(defconst custom-config-org-packages
3333+ '(org)
3434+ )
3535+3636+(defun custom-config-org/org-capture-templates ()
3737+ (setq org-capture-templates '(("m" "micro blog"
3838+ plain
3939+ (file (lambda ()
4040+ (expand-file-name (concat (format-time-string "%Y%m%d%H%M%S")
4141+ ".md")
4242+ "~/Developer/src/personal/blog-hakyll/micro-posts/")))
4343+ "---\npublished : %<%Y-%m-%d %H:%M:%S%z>\n---\n\n%c%?")
4444+ ("n" "new note"
4545+ item
4646+ (file (lambda ()
4747+ (expand-file-name (concat "note-"
4848+ (format-time-string "%Y%m%d%H%M%S")
4949+ ".org")
5050+ org-directory)))
5151+ "**** \n- %?")
5252+ ;; ("p" "new day plan"
5353+ ;; plain
5454+ ;; (file (lambda ()
5555+ ;; (expand-file-name (concat "plan-"
5656+ ;; (format-time-string "%Y-%m-%d")
5757+ ;; ".org")
5858+ ;; "~/Documents/Org-Notes/Plans/")))
5959+ ;; "#+FILETAGS: planning daily\n* %^{item}\n %^{date}T\n - %?\n")
6060+ ("t" "new todo"
6161+ entry
6262+ (file+headline "" "Tasks")
6363+ "* TODO %?\n:PROPERTIES:\n:CREATEDON: %U\n:END:" :prepend t)))
6464+ )
6565+6666+(defun custom-config-org/org-custom-commands ()
6767+ (setq org-agenda-custom-commands
6868+ (quote
6969+ (("A" "Priority #A tasks\"" agenda ""
7070+ ((org-agenda-span
7171+ (quote day))
7272+ (org-agenda-overriding-header "Today's priority #A tasks: ")
7373+ (org-agenda-skip-function
7474+ (quote
7575+ (org-agenda-skip-entry-if
7676+ (quote notregexp)
7777+ "\\=.*\\[#A\\]")))))
7878+ ("b" "Priority #A and #B tasks" agenda ""
7979+ ((org-agenda-span
8080+ (quote day))
8181+ (org-agenda-overriding-header "Today's priority #A and #B tasks: ")
8282+ (org-agenda-skip-function
8383+ (quote
8484+ (org-agenda-skip-entry-if
8585+ (quote regexp)
8686+ "\\=.*\\[#C\\]")))))
8787+ ("u" "Unscheduled tasks" tags "TODO<>\"\"&TODO<>{DONE\\|CANCELED\\|DEFERRED\\|SOMEDAY}"
8888+ ((org-agenda-overriding-header "Unscheduled tasks: ")
8989+ (org-agenda-skip-function
9090+ (quote
9191+ (org-agenda-skip-entry-if
9292+ (quote scheduled)
9393+ (quote deadline)
9494+ )))
9595+ (org-agenda-sorting-strategy
9696+ (quote
9797+ (user-defined-up)))
9898+ (org-agenda-prefix-format "%-11c%5(org-todo-age) ")))
9999+ ))))
100100+101101+(defun custom-config-org/post-init-org ()
102102+ (setq
103103+ org-directory "/Users/kaushik/Documents/Org-Notes/"
104104+105105+ org-default-notes-file "/Users/kaushik/Library/Mobile Documents/iCloud~com~appsonthemove~beorg/Documents/Org-Notes/todo.org"
106106+107107+ org-agenda-files '(
108108+ "/Users/kaushik/Documents/Org-Notes/"
109109+ "/Users/kaushik/Library/Mobile Documents/iCloud~com~appsonthemove~beorg/Documents/Org-Notes/todo.org"
110110+ )
111111+112112+ org-todo-keywords '((sequence
113113+ "TODO(t)"
114114+ "STARTED(s@/!)"
115115+ "WAITING(w@)"
116116+ "|"
117117+ "DONE(d!)"
118118+ "SOMEDAY(y!)"
119119+ "CANCELLED(c@)"
120120+ "DEFERRED(r@)"))
121121+ org-todo-keyword-faces (quote (
122122+ ("TODO" :foreground "#00BFFF" :weight bold)
123123+ ("STARTED" :foreground "#FF8247" :weight bold)
124124+ ("WAITING" :foreground "#EE6363" :weight bold)
125125+ ("SOMEDAY" :foreground "#EEDC82" :weight bold)
126126+ ("DEFERRED" :foreground "#4876FF" :weight bold)
127127+ ("DONE" :foreground "#76EE00" :weight bold)
128128+ ))
129129+130130+ org-pretty-entities t
131131+ org-agenda-span 'day
132132+ org-archive-location "TODO-archive::"
133133+ org-archive-save-context-info (quote (time category itags))
134134+ )
135135+136136+ (custom-config-org/org-capture-templates)
137137+ (custom-config-org/org-custom-commands)
138138+ (add-hook 'org-capture-mode-hook
139139+ (lambda ()
140140+ (evil-insert-state)))
141141+ )
142142+;;; packages.el ends here
+65
spacemacs-private/custom-direnv/packages.el
···11+;;; packages.el --- custom-direnv layer packages file for Spacemacs.
22+;;
33+;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44+;;
55+;; Author: Kaushik Chakraborty <kaushik@AMB00472.local>
66+;; URL: https://github.com/syl20bnr/spacemacs
77+;;
88+;; This file is not part of GNU Emacs.
99+;;
1010+;;; License: GPLv3
1111+1212+;;; Commentary:
1313+1414+;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515+;; a new layer:
1616+;;
1717+;; SPC h SPC layers RET
1818+;;
1919+;;
2020+;; Briefly, each package to be installed or configured by this layer should be
2121+;; added to `custom-direnv-packages'. Then, for each package PACKAGE:
2222+;;
2323+;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424+;; function `custom-direnv/init-PACKAGE' to load and initialize the package.
2525+2626+;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727+;; define the functions `custom-direnv/pre-init-PACKAGE' and/or
2828+;; `custom-direnv/post-init-PACKAGE' to customize the package as it is loaded.
2929+3030+;;; Code:
3131+3232+(defconst custom-direnv-packages
3333+ '(direnv
3434+ flycheck))
3535+3636+(defun custom-direnv/patch-direnv-environment (&rest _args)
3737+ (let ((emacs-bin (directory-file-name
3838+ (file-name-directory
3939+ (executable-find "emacsclient")))))
4040+ (setenv "PATH" (concat emacs-bin ":" (getenv "PATH")))
4141+ (setq exec-path (cons (file-name-as-directory emacs-bin)
4242+ exec-path))))
4343+4444+(defun custom-direnv/init-direnv ()
4545+ (use-package direnv
4646+ :defer t
4747+ :config
4848+ (advice-add 'direnv-update-directory-environment
4949+ :after #'custom-direnv/patch-direnv-environment)
5050+ ))
5151+5252+(defun custom-direnv/post-init-flycheck ()
5353+ '(setq flycheck-executable-find
5454+ (lambda (cmd)
5555+ (add-hook 'post-command-hook #'direnv--maybe-update-environment)
5656+ (direnv-update-environment default-directory)
5757+ (executable-find cmd))))
5858+5959+(defun custom-direnv/post-init-direnv ()
6060+ (direnv-mode)
6161+ (add-hook 'git-commit-mode-hook #'custom-direnv/patch-direnv-environment)
6262+ )
6363+6464+6565+;;; packages.el ends here
+84
spacemacs-private/custom-haskell/packages.el
···11+;;; packages.el --- custom-haskell layer packages file for Spacemacs.
22+;;
33+;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44+;;
55+;; Author: Kaushik Chakraborty <kaushik@AMB00472.local>
66+;; URL: https://github.com/syl20bnr/spacemacs
77+;;
88+;; This file is not part of GNU Emacs.
99+;;
1010+;;; License: GPLv3
1111+1212+;;; Commentary:
1313+1414+;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515+;; a new layer:
1616+;;
1717+;; SPC h SPC layers RET
1818+;;
1919+;;
2020+;; Briefly, each package to be installed or configured by this layer should be
2121+;; added to `custom-haskell-packages'. Then, for each package PACKAGE:
2222+;;
2323+;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424+;; function `custom-haskell/init-PACKAGE' to load and initialize the package.
2525+2626+;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727+;; define the functions `custom-haskell/pre-init-PACKAGE' and/or
2828+;; `custom-haskell/post-init-PACKAGE' to customize the package as it is loaded.
2929+3030+;;; Code:
3131+3232+(defconst custom-haskell-packages
3333+ '()
3434+ "The list of Lisp packages required by the custom-haskell layer.
3535+3636+Each entry is either:
3737+3838+1. A symbol, which is interpreted as a package to be installed, or
3939+4040+2. A list of the form (PACKAGE KEYS...), where PACKAGE is the
4141+ name of the package to be installed or loaded, and KEYS are
4242+ any number of keyword-value-pairs.
4343+4444+ The following keys are accepted:
4545+4646+ - :excluded (t or nil): Prevent the package from being loaded
4747+ if value is non-nil
4848+4949+ - :location: Specify a custom installation location.
5050+ The following values are legal:
5151+5252+ - The symbol `elpa' (default) means PACKAGE will be
5353+ installed using the Emacs package manager.
5454+5555+ - The symbol `local' directs Spacemacs to load the file at
5656+ `./local/PACKAGE/PACKAGE.el'
5757+5858+ - A list beginning with the symbol `recipe' is a melpa
5959+ recipe. See: https://github.com/milkypostman/melpa#recipe-format")
6060+6161+6262+;;; packages.el ends here
6363+6464+(defconst custom-haskell-packages
6565+ '(haskell
6666+ dante
6767+ nix-sandbox))
6868+6969+(defun custom-haskell/post-init-haskell ()
7070+ (add-hook 'haskell-mode-hook 'turn-on-haskell-indentation)
7171+ (add-hook 'haskell-mode-hook 'dante-mode)
7272+ (spacemacs/set-leader-keys-for-major-mode
7373+ 'haskell-mode "M-." 'xref-find-definitions)
7474+ )
7575+(defun custom-haskell/post-init-dante ()
7676+ (add-hook 'dante-mode-hook
7777+ '(lambda ()
7878+ (flycheck-add-next-checker 'haskell-dante
7979+ '(warning . haskell-hlint)))))
8080+8181+(defun custom-haskell/post-init-nix-sandbox ()
8282+ (setq haskell-process-wrapper-function
8383+ (lambda (args) (apply 'nix-shell-command (nix-current-sandbox) args)))
8484+ )
+46
spacemacs-private/custom-lean/README.org
···11+#+TITLE: custom-lean layer
22+33+# The maximum height of the logo should be 200 pixels.
44+[[img/custom-lean.png]]
55+66+# TOC links should be GitHub style anchors.
77+* Table of Contents :TOC_4_gh:noexport:
88+- [[#description][Description]]
99+ - [[#features][Features:]]
1010+- [[#install][Install]]
1111+- [[#key-bindings][Key bindings]]
1212+1313+* Description
1414+This layer adds support for [[https://leanprover.github.io/][Lean]] programming language.
1515+1616+** Features:
1717+ - syntax highlighting
1818+ - autocompletion via ~company-lean~
1919+ - searcheable list of options via ~helm-lean~
2020+2121+* Install
2222+To use this configuration layer, add it to your =~/.spacemacs=. You will need to
2323+add =custom-lean= to the existing =dotspacemacs-configuration-layers= list in this
2424+file.
2525+2626+* Key bindings
2727+2828+ from [[https://github.com/leanprover/lean-mode][lean-mode]]
2929+3030+ | Key | Function |
3131+ |--------------------|---------------------------------------------------------------------------------|
3232+ | <kbd>M-.</kbd> | jump to definition in source file (`lean-find-definition`) |
3333+ | <kbd>M-,</kbd> | jump back to position before <kbd>M-.</kbd> (`xref-pop-marker-stack`) |
3434+ | <kbd>C-c C-k</kbd> | shows the keystroke needed to input the symbol under the cursor |
3535+ | <kbd>C-c C-x</kbd> | execute lean in stand-alone mode (`lean-std-exe`) |
3636+ | <kbd>C-c SPC</kbd> | run a command on the hole at point (`lean-hole`) |
3737+ | <kbd>C-c C-d</kbd> | show a searchable list of definitions (`helm-lean-definitions`) |
3838+ | <kbd>C-c C-g</kbd> | toggle showing current tactic proof goal (`lean-toggle-show-goal`) |
3939+ | <kbd>C-c C-n</kbd> | toggle showing next error in dedicated buffer (`lean-toggle-next-error`) |
4040+ | <kbd>C-c C-b</kbd> | toggle showing output in inline boxes (`lean-message-boxes-toggle`) |
4141+ | <kbd>C-c C-r</kbd> | restart the lean server (`lean-server-restart`) |
4242+ | <kbd>C-c C-s</kbd> | switch to a different Lean version via [elan](https://github.com/Kha/elan) (`lean-server-switch-version`) |
4343+ | <kbd>C-c ! n</kbd> | flycheck: go to next error |
4444+ | <kbd>C-c ! p</kbd> | flycheck: go to previous error |
4545+ | <kbd>C-c ! l</kbd> | flycheck: show list of errors |
4646+
+59
spacemacs-private/custom-lean/packages.el
···11+;;; packages.el --- custom-lean layer packages file for Spacemacs.
22+;;
33+;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44+;;
55+;; Author: Kaushik Chakraborty <git@kaushikc.org>
66+;; URL: https://kaushikc.org
77+;;
88+;; This file is not part of GNU Emacs.
99+;;
1010+;;; License: GPLv3
1111+1212+;;; Commentary:
1313+1414+;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515+;; a new layer:
1616+;;
1717+;; SPC h SPC layers RET
1818+;;
1919+;;
2020+;; Briefly, each package to be installed or configured by this layer should be
2121+;; added to `custom-lean-packages'. Then, for each package PACKAGE:
2222+;;
2323+;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424+;; function `custom-lean/init-PACKAGE' to load and initialize the package.
2525+2626+;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727+;; define the functions `custom-lean/pre-init-PACKAGE' and/or
2828+;; `custom-lean/post-init-PACKAGE' to customize the package as it is loaded.
2929+3030+;;; Code:
3131+3232+(defconst custom-lean-packages
3333+ '(
3434+ lean-mode
3535+ (company-lean :requires company)
3636+ (helm-lean :requires helm)
3737+ ))
3838+3939+(defun custom-lean/init-lean-mode ()
4040+ (use-package lean-mode
4141+ :defer t))
4242+4343+(defun custom-lean/post-init-company ())
4444+4545+(defun custom-lean/init-company-lean ()
4646+ (use-package company-lean
4747+ :defer t
4848+ :init
4949+ (spacemacs|add-company-backends
5050+ :backends company-lean
5151+ :modes lean-mode)
5252+ ))
5353+5454+(defun custom-lean/init-helm-lean ()
5555+ (use-package helm-lean
5656+ :defer t))
5757+5858+5959+;;; packages.el ends here
+58
spacemacs-private/custom-nix/packages.el
···11+;;; packages.el --- custom-nix layer packages file for Spacemacs.
22+;;
33+;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44+;;
55+;; Author: Kaushik Chakraborty <kaushik@AMB00472>
66+;; URL: https://github.com/syl20bnr/spacemacs
77+;;
88+;; This file is not part of GNU Emacs.
99+;;
1010+;;; License: GPLv3
1111+1212+;;; Commentary:
1313+1414+;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515+;; a new layer:
1616+;;
1717+;; SPC h SPC layers RET
1818+;;
1919+;;
2020+;; Briefly, each package to be installed or configured by this layer should be
2121+;; added to `custom-nix-packages'. Then, for each package PACKAGE:
2222+;;
2323+;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424+;; function `custom-nix/init-PACKAGE' to load and initialize the package.
2525+2626+;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727+;; define the functions `custom-nix/pre-init-PACKAGE' and/or
2828+;; `custom-nix/post-init-PACKAGE' to customize the package as it is loaded.
2929+3030+;;; Code:
3131+3232+(defconst custom-nix-packages
3333+ '(nix-mode
3434+ nix-sandbox
3535+ persp-mode)
3636+ )
3737+3838+(defun custom-nix/init-nix-mode ()
3939+ (use-package nix-mode
4040+ :defer t
4141+ :mode ("\\.nix\\'" "\\.nix.in\\'")
4242+ :init
4343+ (setq nix-indent-function 'nix-indent-line)
4444+ ))
4545+4646+(defun custom-nix/init-nix-sandbox ()
4747+ (use-package nix-sandbox
4848+ :defer t))
4949+5050+(defun custom-nix/post-init-persp-mode()
5151+ (spacemacs|define-custom-layout "@Nix-Config"
5252+ :binding "n"
5353+ :body
5454+ (progn
5555+ (find-file "~/Developer/src/personal/nix-config/darwin.nix")
5656+ (find-file-other-window "~/Developer/src/personal/nixpkgs/pkgs/top-level/all-packages.nix"))))
5757+5858+;;; packages.el ends here
+53
spacemacs-private/custom-super-save/packages.el
···11+;;; packages.el --- custom-super-save layer packages file for Spacemacs.
22+;;
33+;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
44+;;
55+;; Author: Kaushik Chakraborty <git@kaushikc.org>
66+;; URL: https://kaushikc.org
77+;;
88+;; This file is not part of GNU Emacs.
99+;;
1010+;;; License: GPLv3
1111+1212+;;; Commentary:
1313+1414+;; See the Spacemacs documentation and FAQs for instructions on how to implement
1515+;; a new layer:
1616+;;
1717+;; SPC h SPC layers RET
1818+;;
1919+;;
2020+;; Briefly, each package to be installed or configured by this layer should be
2121+;; added to `custom-super-save-packages'. Then, for each package PACKAGE:
2222+;;
2323+;; - If PACKAGE is not referenced by any other Spacemacs layer, define a
2424+;; function `custom-super-save/init-PACKAGE' to load and initialize the package.
2525+2626+;; - Otherwise, PACKAGE is already referenced by another Spacemacs layer, so
2727+;; define the functions `custom-super-save/pre-init-PACKAGE' and/or
2828+;; `custom-super-save/post-init-PACKAGE' to customize the package as it is loaded.
2929+3030+;;; Code:
3131+3232+(defconst custom-super-save-packages
3333+ '(super-save)
3434+ )
3535+(defun custom-super-save/init-super-save ()
3636+ (use-package super-save
3737+ :defer t
3838+ :config
3939+ (super-save-mode +1)
4040+ (setq auto-save-default t)
4141+ (setq super-save-remote-files nil)
4242+ (setq super-save-auto-save-when-idle t)
4343+ ))
4444+4545+(defun custom-super-save/post-init-super-save ()
4646+ (super-save-mode +1)
4747+ (setq auto-save-default t)
4848+ (setq super-save-remote-files nil)
4949+ (setq super-save-auto-save-when-idle t)
5050+ )
5151+5252+5353+;;; packages.el ends here
+6
spacemacs-private/local/README.md
···11+# Private directory for local packages
22+33+The content of this directory is ignored by Git.
44+55+This is the place to store the local packages that you define in
66+the `dotspacemacs-additional-packages` variable of your dotfile.
+2123
spacemacs-private/local/beeminder.el
···11+;;; beeminder.el --- Emacs client for Beeminder -*- lexical-binding: t; -*-
22+33+;; Copyright (C) 2015 Marcin 'mbork' Borkowski
44+55+;; Author: Marcin Borkowski <mbork@mbork.pl>
66+;; Keywords: calendar
77+;; Package-Requires: ((request "0.2.0"))
88+99+;; This file is NOT part of GNU Emacs.
1010+1111+;; beeminder.el is free software: you can redistribute it and/or modify
1212+;; it under the terms of the GNU General Public License as published by
1313+;; the Free Software Foundation, either version 3 of the License, or
1414+;; (at your option) any later version.
1515+1616+;; beeminder.el is distributed in the hope that it will be useful,
1717+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
1818+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1919+;; GNU General Public License for more details.
2020+2121+;; You should have received a copy of the GNU General Public License
2222+;; along with beeminder.el.
2323+;; If not, see <http://www.gnu.org/licenses/>.
2424+2525+;;; Commentary:
2626+;; beeminder.el is an Emacs client for the Beeminder service.
2727+;; Beeminder (taglined "a reminder with a sting") is
2828+;; a motivation/quantified self tool based on behavioral economics
2929+;; principles.
3030+3131+(require 'json)
3232+(require 'request)
3333+(if (>= emacs-major-version 24)
3434+ (progn
3535+ (require 'cl-lib)
3636+ (require 'anaphora)
3737+ (defalias 'increasingp '<))
3838+ (require 'cl)
3939+ (defalias 'cl-reduce 'reduce)
4040+ (defalias 'cl-find 'find)
4141+ (defalias 'cl-incf 'incf)
4242+ (defalias 'cl-decf 'decf)
4343+ (defalias 'cl-case 'case)
4444+ (defalias 'cl-delete 'delete*)
4545+ (defmacro setq-local (var val)
4646+ "This is taken from subr.el."
4747+ `(set (make-local-variable ',var) ,val))
4848+ (defmacro aif (cond then &rest else)
4949+ `(let ((it ,cond))
5050+ (if it ,then ,@else)))
5151+ (defmacro awhen (cond &rest body)
5252+ `(aif ,cond
5353+ (progn ,@body)))
5454+ (defun increasingp (&rest args)
5555+ "Return t if ARGS are in increasing order."
5656+ (if (cdr args)
5757+ (and (< (car args) (cadr args))
5858+ (apply #' increasingp (cdr args)))
5959+ t)))
6060+(require 'ewoc)
6161+6262+;;; Code:
6363+6464+6565+;; Utilities
6666+(defun trim-leading-whitespace (string)
6767+ "Trim tabs and spaces from the beginning of STRING."
6868+ (when (string-match "^[ \t]*" string)
6969+ (replace-match "" nil nil string)))
7070+7171+(defun beeminder-alist-get (key alist)
7272+ "Return the value associated to KEY in ALIST.
7373+This function is needed for Emacsen older than v25."
7474+ (cdr (assoc key alist)))
7575+7676+(defun beeminder-set-alist-value (key alist value)
7777+ "Set the value corresponding to KEY in ALIST to VALUE.
7878+Note: ALIST should be a symbol. This is morally equivalent to
7979+`(setf (alist-get key (symbol-value alist)) value)',
8080+but works in older Emacsen."
8181+ (let ((pair (assoc key (symbol-value alist))))
8282+ (if pair
8383+ (setcdr pair value)
8484+ (set alist (acons key value (symbol-value alist))))))
8585+8686+(defun beeminder-inc-alist-value (key alist increment)
8787+ "Increment the value corresponding to KEY in ALIST by INCREMENT.
8888+Throw an error if KEY is not in ALIST."
8989+ (let ((pair (assoc key alist)))
9090+ (if pair
9191+ (incf (cdr pair) increment)
9292+ (error "Nothing to increment"))))
9393+9494+(defun beeminder-to-list (sequence)
9595+ "Turn SEQUENCE into a list."
9696+ (append sequence nil))
9797+9898+9999+;; Settings
100100+101101+(defgroup beeminder nil
102102+ "An Emacs client for Beeminder."
103103+ :group 'applications)
104104+105105+(defcustom beeminder-username ""
106106+ "User name for the Beeminder account."
107107+ :type 'string
108108+ :group 'beeminder)
109109+110110+(defcustom beeminder-auth-token ""
111111+ "Authentication token for Beeminder.
112112+You can retrieve it from the URL
113113+`https://www.beeminder.com/api/v1/auth_token.json'."
114114+ :type 'string
115115+ :group 'beeminder)
116116+117117+(defcustom beeminder-api-url "https://www.beeminder.com/api/v1/users/"
118118+ "The URL for making API calls."
119119+ :type 'string
120120+ :group 'beeminder)
121121+122122+(defvar beeminder-goals nil
123123+ "The list of sexps representing goals.
124124+Updated by `beeminder-get-goals'.")
125125+126126+(defcustom beeminder-default-timeout 30
127127+ "Default timeout for HTTP requests sent to beeminder, in seconds."
128128+ :type 'number
129129+ :group 'beeminder)
130130+131131+132132+;; Beeminder mode
133133+134134+(define-derived-mode beeminder-mode special-mode "Beeminder"
135135+ "A major mode for a buffer with Beeminder goal list.")
136136+137137+(defun next-goal (count)
138138+ "Move COUNT goals forward in the Beeminder buffer."
139139+ (interactive "p")
140140+ (ewoc-goto-next beeminder-goals-ewoc
141141+ (if (beeminder-before-first-goal-p)
142142+ (1- count)
143143+ count)))
144144+145145+(defun previous-goal (count)
146146+ "Move COUNT goals back in the Beeminder buffer.
147147+If on the first goal, move to (point-min)."
148148+ ;; If point is before the place `previous-goal' would move it, move
149149+ ;; to (point-min). This jumps to the beginning from any place
150150+ ;; before the first node, but won't work when point is on Nth goal
151151+ ;; and `count' is greater than N. This doesn't seem a big deal, so
152152+ ;; let's just hope nobody notices that.
153153+ (interactive "p")
154154+ (when (<= (point)
155155+ (progn (ewoc-goto-prev beeminder-goals-ewoc count)
156156+ (point)))
157157+ (goto-char (point-min))))
158158+159159+(define-key beeminder-mode-map (kbd "n") #'next-goal)
160160+(define-key beeminder-mode-map (kbd "p") #'previous-goal)
161161+162162+163163+;; API interface
164164+165165+(defun beeminder-create-api-url (string)
166166+ "Prepend Beeminder site address and the username to STRING.
167167+STRING should begin with a slash."
168168+ (concat beeminder-api-url beeminder-username string))
169169+170170+(defun beeminder-request-get (request &optional params success-fun error-fun timeout)
171171+ "Send a GET REQUEST to beeminder.com, with TIMEOUT.
172172+Add the necessary details (including the username and the auth
173173+token)."
174174+ (request (beeminder-create-api-url request)
175175+ :parser #'json-read
176176+ :params (append params (list (cons "auth_token" beeminder-auth-token)))
177177+ :success success-fun
178178+ :error error-fun
179179+ :timeout (or timeout beeminder-default-timeout)))
180180+181181+(defun beeminder-request-post (request data success-fun error-fun &optional timeout)
182182+ "Send a POST REQUEST with given DATA and TIMEOUT to beeminder.com.
183183+Add the username and the auth token."
184184+ (request (beeminder-create-api-url request)
185185+ :type "POST"
186186+ :data (append data
187187+ (list (cons "auth_token" beeminder-auth-token)))
188188+ :parser #'json-read
189189+ :success success-fun
190190+ :error error-fun
191191+ :timeout (or timeout beeminder-default-timeout)))
192192+193193+(defun beeminder-request-delete (request success-fun error-fun &optional timeout)
194194+ "Send a DELETE request to beeminder.com, with TIMEOUT.
195195+Add the necessary details (username and the auth token)."
196196+ (request-response-data
197197+ (request (concat (beeminder-create-api-url request))
198198+ :params (list (cons "auth_token" beeminder-auth-token))
199199+ :type "DELETE"
200200+ :parser #'json-read
201201+ :success success-fun
202202+ :error error-fun
203203+ :timeout (or timeout beeminder-default-timeout))))
204204+205205+(defun beeminder-request-put (request data success-fun error-fun &optional timeout)
206206+ "Send a PUT request to beeminder.com, with TIMEOUT.
207207+Add the necessary details (username and the auth token)."
208208+ (request-response-data
209209+ (request (beeminder-create-api-url request)
210210+ :type "PUT"
211211+ :data (append data
212212+ (list (cons "auth_token" beeminder-auth-token)))
213213+ :parser #'json-read
214214+ :success success-fun
215215+ :error error-fun
216216+ :timeout (or timeout beeminder-default-timeout))))
217217+218218+219219+;; API calls
220220+221221+(defun last-goal-midnight (goal-deadline now) ; TODO: maybe refactor using beeminder-determine-date!
222222+ "Return the last \"midnight\" for GOAL-DEADLINE, counting from NOW.
223223+GOAL-DEADLINE is an offset from real midnight in seconds, NOW is
224224+a time value."
225225+ (let* ((now-decoded (decode-time now))
226226+ (last-real-midnight (encode-time 0 0 0
227227+ (cadddr now-decoded)
228228+ (nth 4 now-decoded)
229229+ (nth 5 now-decoded)
230230+ (nth 8 now-decoded)))
231231+ (last-midnight (+ goal-deadline
232232+ (if (< goal-deadline 0) (* 24 60 60) 0)
233233+ (time-to-seconds last-real-midnight))))
234234+ (if (> last-midnight
235235+ (time-to-seconds now))
236236+ (- last-midnight (* 24 60 60))
237237+ last-midnight)))
238238+239239+(defun last-user-midnight (now) ; TODO: maybe refactor using beeminder-determine-date!
240240+ "Return the last \"midnight\" counting from NOW, as Unix timestamp.
241241+Take `beeminder-when-the-day-ends' into consideration."
242242+ (let* ((now-decoded (decode-time now))
243243+ (last-real-midnight (encode-time 0 0 0
244244+ (cadddr now-decoded)
245245+ (nth 4 now-decoded)
246246+ (nth 5 now-decoded)
247247+ (nth 8 now-decoded)))
248248+ (last-midnight (+ (time-to-seconds last-real-midnight)
249249+ beeminder-when-the-day-ends)))
250250+ (if (> last-midnight (time-to-seconds now))
251251+ (- last-midnight (* 24 60 60))
252252+ last-midnight)))
253253+254254+(defcustom beeminder-history-length 7
255255+ "Number of days from which to load datapoints.")
256256+257257+(defun beeminder-sum-today-value (datapoints start-time stop-time)
258258+ "Sum the value for DATAPOINTS between START-TIME and STOP-TIME."
259259+ (cl-reduce #'+
260260+ (mapcar (lambda (datapoint)
261261+ (if (< start-time
262262+ (cdr (assoc 'timestamp datapoint))
263263+ stop-time)
264264+ (cdr (assoc 'value datapoint))
265265+ 0))
266266+ datapoints)))
267267+268268+(defun beeminder-get-goals ()
269269+ "Get all the user's Beeminder goals and put them in the
270270+`beeminder-goals' variable."
271271+ (beeminder-log "fetching goals...")
272272+ (beeminder-request-get
273273+ "/goals.json"
274274+ ()
275275+ (cl-function (lambda (&key data &allow-other-keys)
276276+ (beeminder-log "fetching goals.......")
277277+ (let ((goals (beeminder-to-list data))
278278+ (now (beeminder-current-time)))
279279+ (beeminder-request-get
280280+ ".json"
281281+ (list
282282+ (cons "diff_since"
283283+ (number-to-string
284284+ (- (last-user-midnight now)
285285+ (* beeminder-history-length 24 60 60)))))
286286+ (cl-function (lambda (&key data &allow-other-keys)
287287+ (let* ((datapoints ; extract datapoints alone
288288+ (mapcar
289289+ (lambda (goal) ; extract datapoints from a goal from data from API
290290+ (cons (beeminder-alist-get 'slug goal)
291291+ (beeminder-to-list (beeminder-alist-get 'datapoints goal))))
292292+ (cdr (assoc 'goals data))))
293293+ (deadlines ; deadlines alone
294294+ (mapcar
295295+ (lambda (goal)
296296+ (cons (cdr (assoc 'slug goal))
297297+ (cdr (assoc 'deadline goal))))
298298+ goals))
299299+ (today-values
300300+ (mapcar
301301+ (lambda (goal)
302302+ (let ((last-midnight (if beeminder-use-goal-midnight-today-values
303303+ (last-goal-midnight (cdr (assoc (car goal) deadlines))
304304+ now)
305305+ (last-user-midnight now))))
306306+ (cons (car goal)
307307+ (beeminder-sum-today-value (cdr goal) last-midnight (time-to-seconds now)))))
308308+ datapoints)))
309309+ (cl-flet ((beeminder-join-goal-data (goal)
310310+ "Join GOAL data from various sources."
311311+ (let ((slug-str (cdr (assoc 'slug goal))))
312312+ (append (list (cons 'datapoints
313313+ (cdr (assoc slug-str datapoints)))
314314+ (cons 'donetoday
315315+ (cdr (assoc
316316+ (cdr (assoc 'slug goal))
317317+ today-values)))
318318+ (cons 'history-length beeminder-history-length))
319319+ goal))))
320320+ (setq beeminder-goals (mapcar #'beeminder-join-goal-data goals))
321321+ (mapc #'beeminder-clean-goal goals)
322322+ (beeminder-log "fetching goals...done")
323323+ (setq beeminder-reloading-in-progress nil)))))
324324+ #'beeminder-report-fetching-error))))
325325+ #'beeminder-report-fetching-error))
326326+327327+(defun beeminder-clean-goal (goal)
328328+ "Remove GOAL from `beeminder-dirty-alist' if needed.
329329+The heuristics for that is simple: if the current curval is
330330+different than the one recorded in that list, remove it."
331331+ (let ((slug (intern (cdr (assoc 'slug goal)))))
332332+ (if (and (cdr (assoc slug beeminder-dirty-alist))
333333+ (/= (cdr (assoc 'curval goal))
334334+ (cdr (assoc slug beeminder-dirty-alist))))
335335+ (setq beeminder-dirty-alist (assq-delete-all slug beeminder-dirty-alist)))))
336336+337337+(cl-defun beeminder-report-fetching-error (&key error-thrown &allow-other-keys)
338338+ "Report ERROR-THROWN when fetching goals."
339339+ (beeminder-log (format "fetching goals...error: %s" error-thrown))
340340+ (setq beeminder-reloading-in-progress nil))
341341+342342+(defun beeminder-refresh-goal (slug-str)
343343+ "Refresh autodata and graph of the goal named SLUG-STR.
344344+Please do not use unless really necessary, since it creates
345345+a considerable server load."
346346+ (interactive (list (cdr (assoc 'slug (current-or-read-goal)))))
347347+ (beeminder-request-get (concat "/goals/" slug-str "/refresh_graph.json")
348348+ nil
349349+ (cl-function (lambda (&rest _)
350350+ (beeminder-log
351351+ (format
352352+ "goal %s refreshed"
353353+ slug-str))))
354354+ (cl-function (lambda (&rest _)
355355+ (beeminder-log
356356+ (format
357357+ "goal %s could not be refreshed"
358358+ slug-str))))))
359359+360360+(define-key beeminder-mode-map (kbd "G") #'beeminder-refresh-goal)
361361+362362+363363+;; Submitting datapoints
364364+365365+(defun beeminder-read-string (prompt &optional initial-input history default-value inherit-input-method)
366366+ "Replacement for `read-string', showing the default."
367367+ (read-string (if default-value
368368+ (format "%s (default %s): "
369369+ (if (string-match "^\\(.+\\)\\(: \\)$" prompt)
370370+ (match-string 1 prompt)
371371+ prompt)
372372+ default-value)
373373+ prompt)
374374+ initial-input history default-value inherit-input-method))
375375+376376+(defun beeminder-before-first-goal-p ()
377377+ "Return t if the point is before the first goal or if there is
378378+no first goal."
379379+ (aif (ewoc-nth beeminder-goals-ewoc 0)
380380+ (< (point)
381381+ (ewoc-location it))
382382+ t))
383383+384384+(defun beeminder-get-slug (goal)
385385+ "Return the slug of GOAL."
386386+ (intern (cdr (assoc 'slug goal))))
387387+388388+(defun beeminder-slug-to-goal (slug)
389389+ "Return the goal corresponding to SLUG."
390390+ (cl-find slug beeminder-goals :key #'beeminder-get-slug))
391391+392392+(defun beeminder-slug-to-gnode (slug)
393393+ "Return the goal node corresponding to SLUG."
394394+ (let ((gnode (ewoc-nth beeminder-goals-ewoc 0)))
395395+ (while (and gnode
396396+ (not
397397+ (eq slug (beeminder-get-slug (ewoc-data gnode)))))
398398+ (setq gnode (ewoc-next beeminder-goals-ewoc gnode)))
399399+ gnode))
400400+401401+(defvar beeminder-minibuffer-history nil
402402+ "History of goal slug-strs entered through minibuffer.")
403403+404404+(defun beeminder-read-slug (&optional default)
405405+ "Return a slug read from minibuffer or DEFAULT.
406406+DEFAULT should be a symbol."
407407+ (intern (completing-read (if default
408408+ (format "Goal slug (default %s): " default)
409409+ "Goal slug: ")
410410+ (mapcar (lambda (goal)
411411+ (symbol-name (beeminder-get-slug goal)))
412412+ beeminder-goals)
413413+ nil
414414+ t
415415+ nil
416416+ 'beeminder-minibuffer-history
417417+ (if default (symbol-name default)))))
418418+419419+(defun current-or-read-goal ()
420420+ "Return the goal the point is on.
421421+If the point is before the first goal or in a buffer whose mode
422422+is not `beeminder-mode', use `beeminder-read-slug' to ask for the
423423+goal slug and return that goal instead."
424424+ (if (or (not (eq major-mode 'beeminder-mode))
425425+ (beeminder-before-first-goal-p))
426426+ (let ((default (aif (ewoc-nth beeminder-goals-ewoc 0)
427427+ (beeminder-get-slug (ewoc-data it)))))
428428+ (beeminder-slug-to-goal
429429+ (beeminder-read-slug default)))
430430+ (ewoc-data (ewoc-locate beeminder-goals-ewoc))))
431431+432432+(defcustom beeminder-time-format "%FT%T%z"
433433+ "Default time format for Beeminder comments.")
434434+435435+(defun beeminder-current-time-string (&optional timestamp)
436436+ "Return TIMESTAMP (Unix time) as a string.
437437+Use current time by default. Format is hh:mm:ss tz."
438438+ (format-time-string beeminder-time-format
439439+ (or timestamp (beeminder-current-time))))
440440+441441+(defvar beeminder-dirty-alist '()
442442+ "Alist of slugs and \"curval\" values of changed goals.
443443+A goal is put here by `beeminder-submit-datapoint' and cleared
444444+from the list by `beeminder-get-goals' (if the retrieved data are
445445+actually updated, which can take from a few seconds to even a few
446446+minutes).")
447447+448448+(defgroup beeminder-faces nil
449449+ "Faces used be the Beeminder client."
450450+ :group 'beeminder)
451451+452452+(defface beeminder-dirty '((t :slant italic :foreground "grey50"))
453453+ "Face for displaying \"dirty\" goals, i.e., goals for which
454454+a datapoint was submitted but had ot yet been reloaded."
455455+ :group 'beeminder-faces)
456456+457457+(defun ask-for-timestamp (&optional default)
458458+ "Ask the user for the timestamp, and return it as Unix time.
459459+If `org-read-date' is present, use that; if not, fall back to
460460+`safe-date-to-time' and augment the result with current time."
461461+ (time-to-seconds
462462+ (if (fboundp 'org-read-date)
463463+ (org-read-date t t nil nil (beeminder-safe-time default))
464464+ (let (time)
465465+ (while
466466+ (progn (setq time (safe-date-to-time (beeminder-read-string "Date+time: ")))
467467+ (not
468468+ (y-or-n-p
469469+ (format-time-string "Time entered: %c. Confirm? " time)))))
470470+ time))))
471471+472472+(defun beeminder-default-comment (&optional timestamp)
473473+ "Generate the default comment for the given TIMESTAMP."
474474+ (concat
475475+ "via Emacs at "
476476+ (beeminder-current-time-string timestamp)))
477477+478478+(defun beeminder-ask-for-comment (slug-str amount &optional default-comment)
479479+ "Ask the user for the comment for the goal named SLUG-STR.
480480+Include AMOUNT in the question, and default to DEFAULT-COMMENT."
481481+ (beeminder-read-string
482482+ (format "Comment for amount %s for goal %s: " (number-to-human-string amount) slug-str)
483483+ nil nil default-comment))
484484+485485+(defcustom beeminder-ask-for-comment t
486486+ "Non-nil means ask for comment when a goal is submitted.
487487+This also serves as a confirmation that the user actually wants
488488+to submit data to Beeminder (especially that the question
489489+includes the goal slug and amount), so disabling of this option
490490+is discouraged.")
491491+492492+(defun beeminder-make-goal-dirty (slug)
493493+ "Make the goal with SLUG dirty."
494494+ (beeminder-set-alist-value slug
495495+ 'beeminder-dirty-alist
496496+ (cdr (assoc 'curval (beeminder-slug-to-goal slug))))
497497+ (aif (beeminder-slug-to-gnode slug)
498498+ (ewoc-invalidate beeminder-goals-ewoc it)))
499499+500500+(defun beeminder-clear-dirty-goals ()
501501+ "Clear all dirty goals manually.
502502+This may be needed in rare circumstances, namely when
503503+successfully submitting a datapoint of 0."
504504+ (interactive)
505505+ (setq beeminder-dirty-alist ())
506506+ (save-current-goal
507507+ (ewoc-refresh beeminder-goals-ewoc)))
508508+509509+(defun beeminder-submit-datapoint (slug-str value &optional comment timestamp id)
510510+ "Submit a datapoint to Beeminder goal SLUG-STR with AMOUNT.
511511+Additional data are COMMENT and TIMESTAMP (as Unix time). If
512512+COMMENT is nil, then ask the user for the comment. If TIMESTAMP
513513+is nil, assume now. If PRINT-MESSAGE is non-nil, print suitable
514514+messages in the echo area. If ID is non-nil, use it as requestid.
515515+516516+If called interactively, ask for SLUG-STR (with completion) unless the
517517+point is on a goal node. Then, ask for AMOUNT unless the user
518518+provided a numeric argument, in which case take the argument. Then,
519519+ask for COMMENT, proposing a reasonable default, unless the option
520520+`beeminder-ask-for-comment' is nil. If called with a prefix argument
521521+of \\[universal-argument], ask also for TIMESTAMP. If called with
522522+a prefix argument of `-', use previous day as the TIMESTAMP."
523523+ (interactive
524524+ (let* ((slug-str (cdr (assoc 'slug (current-or-read-goal))))
525525+ (yesterdayp (eq current-prefix-arg '-))
526526+ (value (if (numberp current-prefix-arg)
527527+ current-prefix-arg
528528+ (string-to-number (beeminder-read-string
529529+ (format "Datapoint value for %s%s: "
530530+ slug-str
531531+ (if yesterdayp " (yesterday)" ""))
532532+ nil nil "1"))))
533533+ (current-timestamp (time-to-seconds (beeminder-current-time))))
534534+ (list slug-str
535535+ value
536536+ (unless beeminder-ask-for-comment
537537+ (beeminder-default-comment current-timestamp))
538538+ (or (when yesterdayp
539539+ (- current-timestamp (* 24 60 60)))
540540+ (when (consp current-prefix-arg)
541541+ (ask-for-timestamp))
542542+ current-timestamp))))
543543+ (let ((timestamp (or timestamp (time-to-seconds (beeminder-current-time)))))
544544+ (beeminder-log (format "submitting datapoint of %s for goal %s..."
545545+ (number-to-human-string value)
546546+ slug-str))
547547+ (beeminder-request-post (format "/goals/%s/datapoints.json" slug-str)
548548+ (list
549549+ (cons "value" (format "%f" value))
550550+ (cons "comment" (or comment (beeminder-ask-for-comment
551551+ slug-str
552552+ value
553553+ (beeminder-default-comment timestamp))))
554554+ (if id (cons "requestid" id))
555555+ (cons "timestamp" (format "%s" timestamp)))
556556+ (cl-function (lambda (&rest _)
557557+ (beeminder-log (format "submitting datapoint of %s for goal %s...done"
558558+ (number-to-human-string value)
559559+ slug-str))
560560+ (let* ((slug (intern slug-str))
561561+ (goal (beeminder-slug-to-goal slug)))
562562+ (when goal
563563+ (beeminder-inc-alist-value 'donetoday goal value)
564564+ (beeminder-make-goal-dirty slug)))))
565565+ (cl-function (lambda (&rest _)
566566+ (beeminder-log
567567+ (format "submitting datapoint of %s for goal %s...failed"
568568+ (number-to-human-string value)
569569+ slug-str)
570570+ :error))))))
571571+572572+(define-key beeminder-mode-map (kbd "RET") #'beeminder-submit-datapoint)
573573+574574+575575+;; Sorting EWOC
576576+577577+(defun true (&rest args)
578578+ "Always return t (irrespective of ARGS)."
579579+ t)
580580+581581+(defun ewoc-sort (ewoc pred)
582582+ "Sort EWOC, comparing its nodes using PRED.
583583+Since the author of EWOC didn't really care for sorting, and
584584+neither do I, we just first collect the nodes into a list, sort
585585+it using Elisp's sort, and then recreate the EWOC."
586586+ (let ((ewoc-list (ewoc-collect ewoc #'true)))
587587+ (ewoc-filter ewoc #'ignore)
588588+ (mapcar (lambda (node) (ewoc-enter-last ewoc node))
589589+ (sort ewoc-list pred))))
590590+591591+592592+;; Logging
593593+(define-derived-mode beeminder-log-mode special-mode "Beeminder log"
594594+ "A major mode for logging beeminder.el actions.")
595595+596596+(defface beeminder-error '((t :foreground "#800" :weight bold))
597597+ "Face for displaying error notifications.")
598598+599599+(defface beeminder-warning '((t :foreground "#880" :weight bold))
600600+ "Face for displaying warning notifications.")
601601+602602+(defcustom beeminder-notification-expire-time 8
603603+ "After that many seconds less important notifications expire.
604604+TODO: not yet implemented."
605605+ :type 'integer
606606+ :group 'beeminder)
607607+608608+(defvar beeminder-notification-expiration-timer nil
609609+ "Timer used to clear notifications after
610610+`beeminder-notification-expire-time'.")
611611+612612+(defun beeminder-log (message &optional level)
613613+ "Put MESSAGE into the log and possibly into the notification
614614+area. LEVEL can be `:error', `:warning', `:logonly' or `:nolog'.
615615+Messages without any of these levels expire after
616616+`beeminder-notification-expire-time' seconds."
617617+ (let ((level-string (cl-case level
618618+ (:error " error")
619619+ (:warning " warning")
620620+ (t ""))))
621621+ (unless (eq level :nolog)
622622+ (save-excursion
623623+ (setq message (subst-char-in-string ?\n ?\s message t))
624624+ (set-buffer (get-buffer-create "*Beeminder log*"))
625625+ (beeminder-log-mode)
626626+ (goto-char (point-max))
627627+ (let ((inhibit-read-only t))
628628+ (insert (current-time-string) level-string ": " message "\n"))))
629629+ (unless (eq level :logonly)
630630+ (message "Beeminder%s: %s"
631631+ level-string
632632+ message)
633633+ (setq beeminder-notification
634634+ (cond ((eq level :error)
635635+ (propertize message 'face 'beeminder-error))
636636+ ((eq level :warning)
637637+ (propertize message 'face 'beeminder-warning))
638638+ (t message))))
639639+ (awhen beeminder-notification-expiration-timer (cancel-timer it))
640640+ (unless (memq level '(:error :warning))
641641+ (setq beeminder-notification-expiration-timer
642642+ (run-at-time beeminder-notification-expire-time nil #'beeminder-clear-notification)))
643643+ (when beeminder-goals-ewoc (beeminder-refresh-goals-list))))
644644+645645+(defun beeminder-clear-notification ()
646646+ "Clear the notification."
647647+ (interactive)
648648+ (setq beeminder-notification nil)
649649+ (beeminder-refresh-goals-list))
650650+651651+(define-key beeminder-mode-map (kbd "C") #'beeminder-clear-notification)
652652+653653+(defvar beeminder-notification nil
654654+ "A message that should appear right below the header in
655655+Beeminder mode.")
656656+657657+(defun beeminder-pop-log ()
658658+ "Pop the Beeminder log buffer."
659659+ (interactive)
660660+ (pop-to-buffer "*Beeminder log*"))
661661+662662+(define-key beeminder-mode-map (kbd "L") #'beeminder-pop-log)
663663+664664+665665+;; Displaying goals
666666+667667+(defvar beeminder-human-time-use-weekday t
668668+ "Non-nil means that `beeminder-human-time' uses weekday names.
669669+Otherwise, use number of days from today.")
670670+671671+(defvar beeminder-tomorrow-code "tom"
672672+ "The abbreviation for \"tomorrow\".")
673673+674674+(defcustom beeminder-when-the-day-ends (* 6 60 60)
675675+ "Number of seconds from midnight when the day is assumed to end.
676676+Times up to this time will be considered to belong to the
677677+previous day. Note: this should be positive, or weird things
678678+might happen."
679679+ :type 'integer
680680+ :group 'beeminder)
681681+682682+(defun beeminder-plural-ending (number)
683683+ "Return \"s\" if NUMBER not equal to one, and \"\" otherwise."
684684+ (if (= number 1) "" "s"))
685685+686686+(defun beeminder-safe-time (time)
687687+ "Convert TIME to Emacs time format if it is a number."
688688+ (if (numberp time)
689689+ (seconds-to-time time)
690690+ time))
691691+692692+(defun beeminder-time-to-days (time)
693693+ "Compute the number of days from 0001-12-31 BC until TIME.
694694+Take into consideration `beeminder-when-the-day-ends'."
695695+ (time-to-days
696696+ (time-add (beeminder-safe-time time)
697697+ (seconds-to-time (- beeminder-when-the-day-ends)))))
698698+699699+(defun beeminder-human-time (time)
700700+ "Convert (future) TIME to a human-friendly format.
701701+- For today, the time.
702702+- For tomorrow, the string `beeminder-tomorrow-code' (by default) and
703703+ the time.
704704+- For times within a week, abbreviation of the weekday or a plus and
705705+ a number of days (depending on `beeminder-human-time-use-weekday')
706706+ and the time.
707707+- For later times, iso date without time.
708708+Midnight is treated as belonging to the previous day, not the following one."
709709+ (let ((delta (- (beeminder-time-to-days time)
710710+ (beeminder-time-to-days (beeminder-current-time)))))
711711+ (cond ((zerop delta) (format-time-string " %R" time))
712712+ ((= 1 delta) (concat " " beeminder-tomorrow-code
713713+ (format-time-string " %R" time)))
714714+ ((<= delta 7)
715715+ (concat (if beeminder-human-time-use-weekday
716716+ (format-time-string " %a"
717717+ (time-add time
718718+ (seconds-to-time (- beeminder-when-the-day-ends))))
719719+ (format " +%d" delta))
720720+ " "
721721+ (format-time-string "%R" time)))
722722+ (t (format-time-string "%Y-%m-%d"
723723+ (time-add time
724724+ (seconds-to-time (- beeminder-when-the-day-ends))))))))
725725+726726+(defconst beeminder-lanes-to-faces-alist
727727+ '((-2 . beeminder-red) (-1 . beeminder-yellow) (1 . beeminder-blue) (2 . beeminder-green))
728728+ "Alist mapping the (normalized) value of lane to goal colors.")
729729+730730+(defun beeminder-normalize-lane (lane-number)
731731+ "Normalize LANE-NUMBER into the interval -2 .. 2.
732732+This means to return 2 for LANE-NUMBER greater than 2 and -2 for
733733+LANE-NUMBER less than -2."
734734+ (min (max lane-number -2) 2))
735735+736736+(defun beeminder-display-string-field (goal field &optional width invisible)
737737+ "Return GOAL's FIELD (which should be a symbol) as a string.
738738+Optionally use length WIDTH (padded from the right with spaces).
739739+Make it invisible if INVISIBLE is non-nil."
740740+ (let ((text (format (if width
741741+ (format "%%-%d.%ds" width width)
742742+ "%s")
743743+ (cdr (assoc field goal)))))
744744+ (if invisible
745745+ (propertize text 'invisible invisible)
746746+ text)))
747747+748748+(defun beeminder-display-losedate-human (goal)
749749+ "Return the losedate field of GOAL in human-friendly format."
750750+ (beeminder-human-time (seconds-to-time (1+ (cdr (assoc 'losedate goal))))))
751751+752752+(defun beeminder-get-rate (goal)
753753+ "Return the rate of GOAL."
754754+ (elt (cdr (assoc 'mathishard goal)) 2))
755755+756756+(defun beeminder-display-rate (goal)
757757+ "Return the rate of the GOAL (with units), as a string."
758758+ (let ((rate (beeminder-get-rate goal)))
759759+ (format (concat
760760+ (number-to-human-string rate 4)
761761+ "/%s")
762762+ (cdr (assoc 'runits goal)))))
763763+764764+(defun beeminder-display-pledge (goal)
765765+ "Return the pledge of the GOAL, as a string."
766766+ (format "$%.2f" (cdr (assoc 'pledge goal))))
767767+768768+(defcustom beeminder-goal-pp-format
769769+ '((beeminder-display-string-field slug 12)
770770+ " "
771771+ beeminder-display-losedate-human
772772+ " "
773773+ (beeminder-display-string-field limsum 16)
774774+ " "
775775+ beeminder-display-rate
776776+ " "
777777+ beeminder-display-pledge
778778+ " "
779779+ (beeminder-display-string-field title))
780780+ "The format for displaying a Beeminder goal.
781781+It is a list whose elements are either strings, printed verbatim,
782782+either functions, which are then called with one argument (the
783783+goal), or lists, in which case the car of the list is a function
784784+and the cdr the list of arguments it should get after the goal."
785785+ :type 'sexp
786786+ :group 'beeminder)
787787+788788+(defun beeminder-goal-face (goal)
789789+ "Return the face for displaying GOAL."
790790+ (if (beeminder-alist-get (intern (cdr (assoc 'slug goal)))
791791+ beeminder-dirty-alist)
792792+ 'beeminder-dirty
793793+ (cdr (assoc (* (cdr (assoc 'yaw goal))
794794+ (beeminder-normalize-lane (cdr (assoc 'lane goal))))
795795+ beeminder-lanes-to-faces-alist))))
796796+797797+(defun beeminder-goal-representation (goal)
798798+ "The string representation of GOAL, with the face applied."
799799+ (propertize (mapconcat (lambda (field-specifier)
800800+ (cond
801801+ ((functionp field-specifier) (funcall field-specifier goal))
802802+ ((consp field-specifier) (apply (car field-specifier)
803803+ goal
804804+ (cdr field-specifier)))
805805+ ((stringp field-specifier) field-specifier)))
806806+ beeminder-goal-pp-format "")
807807+ 'face
808808+ (beeminder-goal-face goal)))
809809+810810+(defun beeminder-display-first-goal ()
811811+ "Display the first goal in the echo area.
812812+Normally, this should be one of the goals with the nearest
813813+deadline. Caution: if more than one goal has the same deadline,
814814+it is not obvious which one is returned as the first from the
815815+server! You might want to bind this function globally so that
816816+you don't need to enter the Beeminder mode to see the nearest
817817+deadline."
818818+ (interactive)
819819+ (let* ((goal (car beeminder-goals))
820820+ (minutes (/ (- (cdr (assoc 'losedate goal))
821821+ (time-to-seconds (beeminder-current-time)))
822822+ 60)))
823823+ (beeminder-log (format "next goal: %s (%d minute%s left, %s to do)"
824824+ (replace-regexp-in-string
825825+ " \\{2,\\}"
826826+ " "
827827+ (beeminder-goal-representation goal))
828828+ minutes
829829+ (beeminder-plural-ending minutes)
830830+ (let ((limsum (cdr (assoc 'limsum goal))))
831831+ (string-match "[[:digit:]]+\\(\\.[[:digit:]]*\\)?" limsum)
832832+ (match-string 0 limsum))))))
833833+834834+835835+;; Faces for goals
836836+837837+(defface beeminder-green '((t :foreground "#080"))
838838+ "Face for displaying Beeminder goals in green."
839839+ :group 'beeminder-faces)
840840+841841+(defface beeminder-blue '((t :foreground "#008"))
842842+ "Face for displaying Beeminder goals in blue."
843843+ :group 'beeminder-faces)
844844+845845+(defface beeminder-yellow '((t :foreground "#880"))
846846+ "Face for displaying Beeminder goals in green."
847847+ :group 'beeminder-faces)
848848+849849+(defface beeminder-red '((t :foreground "#800"))
850850+ "Face for displaying Beeminder goals in red."
851851+ :group 'beeminder-faces)
852852+853853+854854+;; Beeminder EWOC
855855+856856+(defvar beeminder-goals-ewoc nil)
857857+858858+(defvar beeminder-short-header nil
859859+ "If t, the default header is (extremely) shortened.")
860860+861861+(defun beeminder-toggle-short-header (&optional arg)
862862+ "Toggle shortening the header for Beeminder goal list.
863863+If ARG is positive, shorten the header; otherwise, do not."
864864+ (interactive "P")
865865+ (setq beeminder-short-header
866866+ (if (null arg)
867867+ (not beeminder-short-header)
868868+ (> (prefix-numeric-value arg) 0)))
869869+ (save-current-goal
870870+ (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "")
871871+ (ewoc-refresh beeminder-goals-ewoc)))
872872+873873+(define-key beeminder-mode-map (kbd "=") #'beeminder-toggle-short-header)
874874+875875+(defun beeminder-print-filter (filter)
876876+ "Return a printed representation of FILTER.
877877+It should be an element of `beeminder-current-filters'."
878878+ (funcall (nth 3 (assoc (car filter) beeminder-filters)) (cdr filter)))
879879+880880+(defun beeminder-ewoc-header ()
881881+ "Generate header for the Beeminder EWOC."
882882+ (concat (format (if beeminder-short-header
883883+ "Beeminder goals user:%s goals:%s/%d"
884884+ "Beeminder goals for user %s (%s goals displayed out of %d total)")
885885+ beeminder-username
886886+ (if beeminder-goals-ewoc
887887+ (length (ewoc-collect beeminder-goals-ewoc #'true))
888888+ 0)
889889+ (length beeminder-goals))
890890+ (propertize (concat (format (if beeminder-short-header
891891+ " srt:%s"
892892+ "\nsorting criterion: %s")
893893+ (caddr beeminder-current-sorting-setting))
894894+ (if beeminder-short-header
895895+ (format " e%s"
896896+ (if beeminder-show-everyday "+" "-"))
897897+ (format " everyday goals: %s"
898898+ (if beeminder-show-everyday
899899+ "displayed"
900900+ "omitted")))
901901+ (format (if beeminder-short-header
902902+ " fil:%s"
903903+ (format "\nfilter%s: %%s\n"
904904+ (beeminder-plural-ending
905905+ (length beeminder-current-filters))))
906906+ (if beeminder-current-filters
907907+ (mapconcat #'beeminder-print-filter
908908+ beeminder-current-filters
909909+ ", ")
910910+ "none")))
911911+ 'face 'shadow)
912912+ (aif beeminder-notification (concat " " it) "")))
913913+914914+(defun beeminder-create-goals-ewoc ()
915915+ "Return a newly created EWOC for Beeminder goals."
916916+ (ewoc-create (lambda (goal) (insert (beeminder-goal-representation goal)))
917917+ (beeminder-ewoc-header)))
918918+919919+(defun beeminder-populate-ewoc ()
920920+ "Populate Beeminder EWOC using the goal list.
921921+In particular, apply filtering and sorting settings. Note: since
922922+only the last sorting criterion is remembered, and sorting is
923923+stable, this might actually change the ordering of goals, which
924924+may have been sorted by another criterion previously."
925925+ (ewoc-filter beeminder-goals-ewoc #'ignore)
926926+ (mapcar (lambda (goal)
927927+ (ewoc-enter-last beeminder-goals-ewoc goal))
928928+ beeminder-goals)
929929+ (beeminder-apply-filters)
930930+ (apply #'beeminder-sort-by-field beeminder-current-sorting-setting)
931931+ (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "")
932932+ (ewoc-refresh beeminder-goals-ewoc)
933933+ (with-current-buffer (ewoc-buffer beeminder-goals-ewoc)
934934+ (goto-char (point-min))))
935935+936936+(defun beeminder-list-goals ()
937937+ "Switch to a buffer containing the list of Beeminder goals."
938938+ (interactive)
939939+ (aif (get-buffer "*Beeminder goals*")
940940+ (switch-to-buffer it)
941941+ (switch-to-buffer "*Beeminder goals*")
942942+ (buffer-disable-undo)
943943+ (beeminder-mode))
944944+ (let ((inhibit-read-only t))
945945+ (erase-buffer)
946946+ (setq beeminder-goals-ewoc (beeminder-create-goals-ewoc))
947947+ (beeminder-populate-ewoc))
948948+ (setq truncate-lines t)
949949+ (unless beeminder-goals
950950+ (beeminder-get-goals)))
951951+952952+953953+;; Current time function
954954+955955+(defalias 'beeminder-current-time 'current-time
956956+ "An alias for current-time, useful for testing/debugging.")
957957+958958+959959+;; Sorting
960960+961961+(defcustom beeminder-default-sorting-setting (list 'losedate #'< "losedate")
962962+ "Default sorting setting for Beeminder goals.
963963+This is a list whose first element is the field according to
964964+which the sorting should be done, then the predicate, and then
965965+the printed representation of this sorting method (as a string)."
966966+ :type 'sexp
967967+ :group 'beeminder)
968968+969969+(defvar beeminder-current-sorting-setting beeminder-default-sorting-setting)
970970+971971+(defmacro save-current-goal (&rest body)
972972+ "Evaluate BODY and bring the point back to the current goal.
973973+If the Beeminder EWOC disappeared (for some reason), just
974974+evaluate the body."
975975+ (declare (indent 0) (debug t))
976976+ `(if beeminder-goals-ewoc
977977+ (with-current-buffer (ewoc-buffer beeminder-goals-ewoc)
978978+ (let* ((current-goal-slug
979979+ (if (beeminder-before-first-goal-p)
980980+ nil
981981+ (beeminder-get-slug (ewoc-data (ewoc-locate beeminder-goals-ewoc)))))
982982+ (current-line (unless current-goal-slug (line-number-at-pos))))
983983+ ,@body
984984+ (cond ((not current-goal-slug)
985985+ (goto-char (point-min))
986986+ (forward-line (1- current-line)))
987987+ (t
988988+ (ewoc-goto-node beeminder-goals-ewoc (ewoc-nth beeminder-goals-ewoc 0))
989989+ (let ((current-node (ewoc-nth beeminder-goals-ewoc 0)))
990990+ (while (and current-node
991991+ (not (eq (beeminder-get-slug (ewoc-data current-node))
992992+ current-goal-slug)))
993993+ (ewoc-goto-next beeminder-goals-ewoc 1)
994994+ (setq current-node (ewoc-next beeminder-goals-ewoc current-node)))
995995+ (unless current-node (goto-char (point-min))))))))
996996+ ,@body))
997997+998998+(defun beeminder-sort-by-field (field predicate info)
999999+ "Sort entries in `beeminder-goals-ewoc' by FIELD, using PREDICATE.
10001000+INFO is the printed representation of the sorting criterion."
10011001+ (save-current-goal
10021002+ (ewoc-sort beeminder-goals-ewoc (lambda (x y)
10031003+ (funcall predicate
10041004+ (cdr (assoc field x))
10051005+ (cdr (assoc field y)))))
10061006+ (ewoc-refresh beeminder-goals-ewoc)
10071007+ (setq beeminder-current-sorting-setting (list field predicate info))
10081008+ (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "")))
10091009+10101010+(defun beeminder-sort-by-losedate ()
10111011+ "Sort entries in `beeminder-goals' by losedate."
10121012+ (interactive)
10131013+ (beeminder-sort-by-field 'losedate #'< "losedate"))
10141014+10151015+(defun beeminder-seconds-to-from-midnight (time)
10161016+ "Convert TIME to seconds from midnight.
10171017+If after 6:00, convert to seconds to midnight (with a minus
10181018+sign). The magic time constant 6:00 is the result of Beeminder's way
10191019+of dealing with the \"midnight\" setting."
10201020+ (let* ((decoded-time (decode-time time))
10211021+ (seconds (+ (car decoded-time)
10221022+ (* 60 (cadr decoded-time))
10231023+ (* 3600 (caddr decoded-time)))))
10241024+ (if (> seconds (* 6 60 60))
10251025+ (- seconds (* 24 60 60))
10261026+ seconds)))
10271027+10281028+(defun beeminder-earlier-midnight (sec1 sec2 time)
10291029+ "Compare SEC1 and SEC2, taking into account the TIME.
10301030+All three parameters are expressed as seconds from midnight, like
10311031+the result of calling `beeminder-seconds-to-from-midnight'. If
10321032+SEC1 < SEC2 < TIME, return t. If TIME < SEC1 < SEC2, return t.
10331033+If SEC2 < TIME < SEC1, return t. In all other cases, return nil.
10341034+This function is useful for sorting goals by their \"midnight\"
10351035+setting, with the goals which are after their \"midnight\" at the
10361036+end."
10371037+ (or (increasingp sec1 sec2 time)
10381038+ (increasingp time sec1 sec2)
10391039+ (increasingp sec2 time sec1)))
10401040+10411041+(defun beeminder-sort-by-midnight ()
10421042+ "Sort entries in `beeminder-goals' by their midnight, taking current time into consideration."
10431043+ (interactive)
10441044+ (beeminder-sort-by-field
10451045+ 'deadline
10461046+ (lambda (x y)
10471047+ (beeminder-earlier-midnight
10481048+ x y (beeminder-seconds-to-from-midnight
10491049+ (beeminder-current-time))))
10501050+ "midnight"))
10511051+10521052+(define-key beeminder-mode-map "l" #'beeminder-sort-by-losedate)
10531053+(define-key beeminder-mode-map "m" #'beeminder-sort-by-midnight)
10541054+10551055+10561056+;; Refreshing view and reloading goals
10571057+10581058+(defun beeminder-refresh-goals-list ()
10591059+ "Refresh the goals list."
10601060+ (interactive)
10611061+ (save-current-goal
10621062+ (beeminder-populate-ewoc)))
10631063+10641064+(defvar beeminder-reloading-in-progress nil
10651065+ "Non-nil if currently reloading data from the server.")
10661066+10671067+(defun beeminder-clear-reloading-in-progress-flag ()
10681068+ "Clear the `beeminder-reloading-in-progress' flag.
10691069+Useful in case of an error."
10701070+ (interactive)
10711071+ (setq beeminder-reloading-in-progress nil))
10721072+10731073+(defun beeminder-reload-goals-list (&optional force)
10741074+ "Reload the goals from the server.
10751075+With a prefix argument, do it even if reloading is in progress
10761076+\(this is useful when the `beeminder-reloading-in-progress'
10771077+variable is somehow messed up)."
10781078+ (interactive "P")
10791079+ (if (and beeminder-reloading-in-progress
10801080+ (not force))
10811081+ (beeminder-log "fetching goals already in progress, please wait!")
10821082+ (setq beeminder-reloading-in-progress t)
10831083+ (beeminder-get-goals)))
10841084+10851085+(define-key beeminder-mode-map (kbd "C-l") #'beeminder-refresh-goals-list)
10861086+(define-key beeminder-mode-map "g" #'beeminder-reload-goals-list)
10871087+10881088+10891089+;; Filtering goals
10901090+10911091+(defvar beeminder-current-filters '()
10921092+ "Alist of filters currently in effect.")
10931093+10941094+(defcustom beeminder-saved-filters '()
10951095+ "A remembered set of filters for fast retrieval.")
10961096+10971097+(defun beeminder-save-filters ()
10981098+ "Save the current filters."
10991099+ (interactive)
11001100+ (setq beeminder-saved-filters
11011101+ (copy-alist beeminder-current-filters))
11021102+ (beeminder-log (format "current filter settings %s saved." beeminder-current-filters)))
11031103+11041104+(defun beeminder-retrieve-filters ()
11051105+ "Retrieve saved filters."
11061106+ (interactive)
11071107+ (setq beeminder-current-filters
11081108+ (copy-alist beeminder-saved-filters))
11091109+ (beeminder-refresh-goals-list)
11101110+ (beeminder-log (format "filter settings %s retrieved." beeminder-current-filters)))
11111111+11121112+(defun beeminder-clear-filters ()
11131113+ "Clear all filters.
11141114+If there are no saved filters, first save the current filters."
11151115+ (interactive)
11161116+ (unless beeminder-saved-filters
11171117+ (beeminder-save-filters))
11181118+ (setq beeminder-current-filters '())
11191119+ (beeminder-refresh-goals-list))
11201120+11211121+(define-key beeminder-mode-map "c" #'beeminder-clear-filters)
11221122+11231123+(define-prefix-command 'beeminder-filter-map)
11241124+(define-key beeminder-mode-map "f" #'beeminder-filter-map)
11251125+(define-key beeminder-filter-map "c" #'beeminder-clear-filters)
11261126+(define-key beeminder-filter-map "s" #'beeminder-save-filters)
11271127+(define-key beeminder-filter-map "f" #'beeminder-retrieve-filters)
11281128+(define-key beeminder-filter-map "r" #'beeminder-retrieve-filters)
11291129+11301130+(defcustom beeminder-default-filter-days 3
11311131+ "Defalt number of days used for filtering by losedate.
11321132+If the user doesn't specify the number of days for filtering, all
11331133+goals with more than this amount of days left to losedate will be
11341134+filtered out."
11351135+ :type 'integer
11361136+ :group 'beeminder)
11371137+11381138+(defcustom beeminder-use-goal-midnight-today-values nil
11391139+ "If non-nil, compute today's values using the goal's midnight.
11401140+If nil, use the global midnight defined by
11411141+`beeminder-when-the-day-ends'."
11421142+ :type 'boolean
11431143+ :group 'beeminder)
11441144+11451145+(defcustom beeminder-default-filter-donetoday 100
11461146+ "Default percentage of donetoday used for filtering."
11471147+ :type 'integer
11481148+ :group 'beeminder)
11491149+11501150+(defcustom beeminder-default-filter-urgent-hours 8
11511151+ "Default time (in hours) to deadline to consider a goal urgent."
11521152+ :type 'integer
11531153+ :group 'beeminder)
11541154+11551155+(defcustom beeminder-show-dirty-donetoday t
11561156+ "If non-nil, show dirty goals even if they would be normally
11571157+ filtered out by the \"donetoday\" filter."
11581158+ :type 'boolean
11591159+ :group 'beeminder)
11601160+11611161+(defcustom beeminder-show-everyday t
11621162+ "If non-nil, show \"everyday goals\" irrespective of the
11631163+ \"days\" filter.")
11641164+11651165+(defcustom beeminder-everyday-goals-list
11661166+ '()
11671167+ "A list of slugs of \"everyday goals\". These are the goals which
11681168+should be done every day, so even when filtering goals with deadline
11691169+after some number of days, they should be shown.")
11701170+11711171+(defun beeminder-toggle-show-everyday (arg)
11721172+ "Toggle showing \"everyday goals\" if ARG is zero or nil.
11731173+If ARG is positive, turn it on; if negative, off."
11741174+ (interactive "P")
11751175+ (let ((narg (prefix-numeric-value arg)))
11761176+ (cond ((null arg)
11771177+ (setq beeminder-show-everyday (not beeminder-show-everyday)))
11781178+ ((> narg 0)
11791179+ (setq beeminder-show-everyday t))
11801180+ ((< narg 0)
11811181+ (setq beeminder-show-everyday nil))))
11821182+ (beeminder-refresh-goals-list))
11831183+11841184+(define-key beeminder-mode-map "e" #'beeminder-toggle-show-everyday)
11851185+(define-key beeminder-filter-map "e" #'beeminder-toggle-show-everyday)
11861186+11871187+(defun beeminder-days-p (goal days)
11881188+ "Return nil if time to derailment of GOAL > DAYS.
11891189+If DAYS is negative, return nil if time to derailment of GOAL is
11901190+<= -DAYS. If the goal is in `beeminder-everyday-goals-list',
11911191+return t anyway."
11921192+ (if (and beeminder-show-everyday
11931193+ (memq (beeminder-get-slug goal) beeminder-everyday-goals-list))
11941194+ t
11951195+ (let ((days-left (- (beeminder-time-to-days (cdr (assoc 'losedate goal)))
11961196+ (beeminder-time-to-days (beeminder-current-time)))))
11971197+ (if (>= days 0)
11981198+ (<= days-left days)
11991199+ (>= days-left (- days))))))
12001200+12011201+(defun beeminder-donetoday-p (goal percentage)
12021202+ "Return nil if donetoday for GOAL >= PERCENTAGE * day's amount.
12031203+If PERCENTAGE is negative, return nil if donetoday of GOAL is
12041204+less than PERCENTAGE * day's amount. Take the variable
12051205+`beeminder-show-dirty-donetoday' into account."
12061206+ (if (and beeminder-show-dirty-donetoday
12071207+ (beeminder-alist-get (beeminder-get-slug goal) beeminder-dirty-alist))
12081208+ t
12091209+ (let* ((rate (beeminder-get-rate goal))
12101210+ (daily-rate (/ rate
12111211+ (cl-case (intern (cdr (assoc 'runits goal)))
12121212+ (y 365.0)
12131213+ (m (/ 365.0 12))
12141214+ (w 7.0)
12151215+ (d 1.0)
12161216+ (h (/ 1 24.0)))))
12171217+ (100*donetoday (* 100 (cdr (assoc 'donetoday goal))))
12181218+ (percentage*daily-rate (* percentage daily-rate)))
12191219+ (when (> rate 0)
12201220+ (cond ((> percentage 0)
12211221+ (< 100*donetoday
12221222+ percentage*daily-rate))
12231223+ ((zerop percentage)
12241224+ (zerop 100*donetoday))
12251225+ (t
12261226+ (>= 100*donetoday (- percentage*daily-rate))))))))
12271227+12281228+(defun beeminder-calculate-midnight-offset (seconds)
12291229+ "Add (* 24 60 60) to SECONDS if negative."
12301230+ (if (> seconds 0)
12311231+ seconds
12321232+ (+ (* 24 60 60) seconds)))
12331233+12341234+(defun beeminder-urgent-p (goal hours)
12351235+ "Return nil if time to deadline for GOAL is > HOURS.
12361236+If HOURS is negative or zero, return nil if time to deadline is
12371237+<= -HOURS."
12381238+ (let* ((deadline (beeminder-calculate-midnight-offset (beeminder-alist-get 'deadline goal)))
12391239+ (now (decode-time (beeminder-current-time)))
12401240+ (now-sec (car now))
12411241+ (now-min (cadr now))
12421242+ (now-hour (caddr now))
12431243+ (now-time (+ now-sec (* 60 now-min) (* 3600 now-hour)))
12441244+ (time-to-deadline (beeminder-calculate-midnight-offset (- deadline now-time))))
12451245+ (if (> hours 0)
12461246+ (<= time-to-deadline (* 3600 hours))
12471247+ (> time-to-deadline (* -3600 hours)))))
12481248+12491249+(defun beeminder-not-killed-p (goal kill-list)
12501250+ "Return nil if GOAL is in the KILL-LIST."
12511251+ (not (member (beeminder-get-slug goal) kill-list)))
12521252+12531253+(defvar beeminder-filters `((losedate ,#'beeminder-days-p
12541254+ ,beeminder-default-filter-days
12551255+ (lambda (days)
12561256+ (format (if beeminder-short-header
12571257+ "d2d(%s%d)"
12581258+ "days to derailment (%s%d)")
12591259+ (if (>= days 0) "<=" ">=")
12601260+ (abs days))))
12611261+ (donetoday ,#'beeminder-donetoday-p
12621262+ ,beeminder-default-filter-donetoday
12631263+ (lambda (donetoday)
12641264+ (format (if beeminder-short-header
12651265+ "dt(%s%d%%)"
12661266+ "done today (%s%d%%)")
12671267+ (cond ((> donetoday 0) "<")
12681268+ ((zerop donetoday) "=")
12691269+ (t ">="))
12701270+ (abs donetoday))))
12711271+ (urgent ,#'beeminder-urgent-p
12721272+ ,beeminder-default-filter-urgent-hours
12731273+ (lambda (hours)
12741274+ (format (if beeminder-short-header
12751275+ "u(%s%dh)"
12761276+ "urgent (%s%d hours to deadline)")
12771277+ (if (> hours 0) "<=" ">")
12781278+ (abs hours))))
12791279+ (killed ,#'beeminder-not-killed-p
12801280+ '()
12811281+ (lambda (kill-list)
12821282+ (format (if beeminder-short-header
12831283+ "%dgk"
12841284+ "%d goal%s killed")
12851285+ (length kill-list)
12861286+ (beeminder-plural-ending (length kill-list))))))
12871287+12881288+ "List of possible filters. Each element is a list, consisting of:
12891289+- symbol, denoting the filter,
12901290+- predicate (with two arguments - the goal and the parameter),
12911291+- default value for the parameter,
12921292+- formatting function (with one argument - the parameter).")
12931293+12941294+(defun beeminder-kill-goal (gnode)
12951295+ "Delete GNODE from `beeminder-goals-ewoc'."
12961296+ (interactive (list (beeminder-slug-to-gnode (intern (cdr (assoc 'slug (current-or-read-goal)))))))
12971297+ (if gnode
12981298+ (let ((inhibit-read-only t)
12991299+ (next-goal (or (ewoc-next beeminder-goals-ewoc gnode)
13001300+ (ewoc-prev beeminder-goals-ewoc gnode))))
13011301+ (ewoc-delete beeminder-goals-ewoc gnode)
13021302+ (ewoc-refresh beeminder-goals-ewoc)
13031303+ (beeminder-set-alist-value 'killed
13041304+ 'beeminder-current-filters
13051305+ (cons (beeminder-get-slug (ewoc-data gnode))
13061306+ (beeminder-alist-get 'killed beeminder-current-filters)))
13071307+ (ewoc-set-hf beeminder-goals-ewoc (beeminder-ewoc-header) "")
13081308+ (if next-goal
13091309+ (ewoc-goto-node beeminder-goals-ewoc next-goal)
13101310+ (goto-char (point-min)))
13111311+ (beeminder-log (format "goal %s killed (hidden from view)." (cdr (assoc 'slug (ewoc-data gnode))))))
13121312+ (beeminder-log (format "goal %s already killed." (cdr (assoc 'slug (ewoc-data gnode)))))))
13131313+13141314+(define-key beeminder-mode-map (kbd "C-k") #'beeminder-kill-goal)
13151315+(define-key beeminder-filter-map "k" #'beeminder-kill-goal)
13161316+13171317+(defun beeminder-show-kills ()
13181318+ "Show all killed goals."
13191319+ (interactive)
13201320+ (beeminder-log (format "killed goals: %s."
13211321+ (aif (beeminder-alist-get 'killed beeminder-current-filters)
13221322+ (mapconcat #'symbol-name it ", ")
13231323+ "none"))))
13241324+13251325+(defun beeminder-clear-kills ()
13261326+ "Unkill all killed goals."
13271327+ (interactive)
13281328+ (setq beeminder-current-filters
13291329+ (assq-delete-all 'killed beeminder-current-filters))
13301330+ (beeminder-refresh-goals-list))
13311331+13321332+(defun beeminder-clear-or-show-kills (arg)
13331333+ "Unkill all killed goals if ARG is nil.
13341334+With prefix argument, show the list of killed goals."
13351335+ (interactive "P")
13361336+ (if arg
13371337+ (beeminder-show-kills)
13381338+ (beeminder-clear-kills)))
13391339+13401340+(define-key beeminder-mode-map (kbd "C-y") #'beeminder-clear-or-show-kills)
13411341+(define-key beeminder-filter-map "y" #'beeminder-clear-or-show-kills)
13421342+13431343+(defun beeminder-apply-filter (filter)
13441344+ "Apply FILTER (a dotted pair of symbol and parameter).
13451345+This means deleting some goals from `beeminder-goals-ewoc'."
13461346+ (save-current-goal
13471347+ (ewoc-filter beeminder-goals-ewoc
13481348+ (lambda (goal)
13491349+ (funcall (cadr (assoc (car filter) beeminder-filters))
13501350+ goal (cdr filter))))))
13511351+13521352+(defun beeminder-apply-filters ()
13531353+ "Apply filters from `beeminder-current-filters' in sequence."
13541354+ (mapc #'beeminder-apply-filter beeminder-current-filters))
13551355+13561356+(defun beeminder-enable-filter (filter parameter)
13571357+ "Enable FILTER (symbol) with PARAMETER (number).
13581358+Disable FILTER if PARAMETER is nil."
13591359+ (beeminder-set-alist-value filter 'beeminder-current-filters parameter)
13601360+ (setq beeminder-current-filters (rassq-delete-all nil beeminder-current-filters))
13611361+ (beeminder-refresh-goals-list))
13621362+13631363+(defun beeminder-filter-parameter (raw-prefix default)
13641364+ "Return filter parameter based on RAW-PREFIX and DEFAULT."
13651365+ (cond ((eq raw-prefix '-) nil)
13661366+ ((null raw-prefix) default)
13671367+ (t (prefix-numeric-value raw-prefix))))
13681368+13691369+(defun beeminder-filter-by-losedate (&optional days)
13701370+ "Filter out goals with time to losedate greater than DAYS."
13711371+ (interactive "P")
13721372+ (beeminder-enable-filter 'losedate
13731373+ (beeminder-filter-parameter days
13741374+ beeminder-default-filter-days)))
13751375+13761376+(defun beeminder-filter-by-donetoday (&optional percentage)
13771377+ "Filter out goals with donetoday greater than PERCENTAGE."
13781378+ (interactive "P")
13791379+ (beeminder-enable-filter 'donetoday
13801380+ (beeminder-filter-parameter percentage
13811381+ beeminder-default-filter-donetoday)))
13821382+13831383+(defun beeminder-filter-by-urgent (&optional hours)
13841384+ "Filter out goals with time to deadline greater than HOURS."
13851385+ (interactive "P")
13861386+ (beeminder-enable-filter 'urgent
13871387+ (beeminder-filter-parameter hours
13881388+ beeminder-default-filter-urgent-hours)))
13891389+13901390+(define-key beeminder-mode-map (kbd "d") #'beeminder-filter-by-losedate)
13911391+(define-key beeminder-filter-map (kbd "d") #'beeminder-filter-by-losedate)
13921392+(define-key beeminder-mode-map (kbd "t") #'beeminder-filter-by-donetoday)
13931393+(define-key beeminder-filter-map (kbd "t") #'beeminder-filter-by-donetoday)
13941394+(define-key beeminder-mode-map (kbd "u") #'beeminder-filter-by-urgent)
13951395+(define-key beeminder-filter-map (kbd "u") #'beeminder-filter-by-urgent)
13961396+13971397+13981398+;; Displaying goal details
13991399+14001400+(defcustom beeminder-goal-template-fields-alist
14011401+ '((slug . (propertize (symbol-name (beeminder-get-slug goal)) 'face (beeminder-goal-face goal)))
14021402+ (limsum . (propertize (cdr (assoc 'limsum goal)) 'face (beeminder-goal-face goal)))
14031403+ (backburnerp . (if (string= (cdr (assoc 'burner goal)) "backburner") "(backburner)" ""))
14041404+ (username . beeminder-username)
14051405+ (dirtyp . (if (assoc (beeminder-get-slug goal) beeminder-dirty-alist)
14061406+ (propertize " (goal dirty!)" 'face 'beeminder-dirty) ""))
14071407+ (target . (highlight-subtly (number-to-string (elt (cdr (assoc 'mathishard goal)) 1))))
14081408+ (goaldate . (highlight-subtly (format-time-string "%x" (seconds-to-time (elt (cdr (assoc 'mathishard goal)) 0)))))
14091409+ (rate . (highlight-subtly (number-to-human-string (beeminder-get-rate goal))))
14101410+ (runit . (highlight-subtly
14111411+ (cl-case (intern (cdr (assoc 'runits goal)))
14121412+ (d "day")
14131413+ (w "week")
14141414+ (m "month")
14151415+ (h "hour")
14161416+ (y "year"))))
14171417+ (curval . (highlight-subtly (number-to-human-string (cdr (assoc 'curval goal)))))
14181418+ (autodatap . (aif (cdr (assoc 'autodata goal))
14191419+ (concat ", autodata source: "
14201420+ (highlight-subtly it))
14211421+ ""))
14221422+ (goaltype . (highlight-subtly (beeminder-display-goal-type (cdr (assoc 'goal_type goal)))))
14231423+ (losedate . (highlight-subtly (trim-leading-whitespace
14241424+ (beeminder-display-losedate-human goal))))
14251425+ (pledge . (highlight-subtly (beeminder-display-pledge goal)))
14261426+ (midnight . (highlight-subtly (beeminder-display-midnight-setting (cdr (assoc 'deadline goal)))))
14271427+ (donetoday . (highlight-subtly (number-to-human-string (cdr (assoc 'donetoday goal)))))
14281428+ (datapoints . (propertize (beeminder-format-datapoints goal) 'face
14291429+ 'shadow))
14301430+ (history-length . (highlight-subtly (let ((hl (cdr (assoc 'history-length goal))))
14311431+ (if (zerop hl) "all" (format "%s" hl))))))
14321432+ "Alist of symbols and corresponding pieces of code to evaluate
14331433+and insert the result in the goal details info.")
14341434+14351435+(defun beeminder-display-time-field (alist field)
14361436+ "Return ALIST's (unix-time) FIELD formatted."
14371437+ (format-time-string "%x %X" (seconds-to-time (cdr (assoc field alist)))))
14381438+14391439+(defcustom beeminder-datapoint-format
14401440+ '((beeminder-display-string-field id 25 t)
14411441+ (beeminder-display-time-field timestamp)
14421442+ " "
14431443+ (beeminder-display-string-field value 8)
14441444+ " "
14451445+ (beeminder-display-string-field comment))
14461446+ "The format for displaying a goal's datapoint.
14471447+The format is identical to that of `beeminder-goal-pp-format'.")
14481448+14491449+(defun beeminder-datapoint-representation (datapoint)
14501450+ "The string representation of DATAPOINT."
14511451+ ;; TODO: factor out common code of this and `beeminder-goal-pp-format'.
14521452+ (mapconcat (lambda (field-specifier)
14531453+ (cond
14541454+ ((functionp field-specifier) (funcall field-specifier datapoint))
14551455+ ((consp field-specifier) (apply (car field-specifier)
14561456+ datapoint
14571457+ (cdr field-specifier)))
14581458+ ((stringp field-specifier) field-specifier)))
14591459+ beeminder-datapoint-format ""))
14601460+14611461+(defun beeminder-format-datapoints (goal)
14621462+ "Return the printed representation of GOAL's datapoints."
14631463+ (mapconcat #'beeminder-datapoint-representation
14641464+ (reverse (cdr (assoc 'datapoints goal)))
14651465+ "\n"))
14661466+14671467+(defun beeminder-insert-goal-template-with-expansion (template goal)
14681468+ "Insert TEMPLATE with information about GOAL.
14691469+If a substring of the form \"#SYMBOL\" is found, and SYMBOL is
14701470+a key in the `beeminder-goal-template-fields-alist' variable,
14711471+\"#SYMBOL\" is replaced with result of evaluating the associated
14721472+value. If there is no such entry, the SYMBOL is looked up in the
14731473+alist representing the current goal, and \"#SYMBOL\" is replaced
14741474+with its printed representation (using `format''s \"%s\"
14751475+specifier). In the latter case, the result is colorized with the
14761476+`subtle-highlight-face'; in the former case, code in
14771477+`beeminder-goal-template-fields-alist' should take care of
14781478+colorization if needed.
14791479+14801480+If a substring of the form \"#SEXP\" is found, and SEXP is not
14811481+a symbol, \"#SEXP\" is replaced with the result of evaluating SEXP.
14821482+Within SEXP, the variable `goal' is bound to the alist holding the
14831483+current's goal properties.
14841484+14851485+Should someone want to insert a literal \"#\" character, the form
14861486+\"#(identity \"#\")\" can be used.
14871487+14881488+Warning: this function uses `eval', so evil code in TEMPLATE or
14891489+`beeminder-goal-template-fields-alist' can do real harm!"
14901490+ (save-excursion (insert template))
14911491+ (while (search-forward "#" nil t)
14921492+ (let ((begin (1- (point)))
14931493+ (sexp (read (current-buffer))))
14941494+ (delete-region begin (point))
14951495+ (insert (format "%s"
14961496+ (cond ((symbolp sexp)
14971497+ (aif (assoc sexp beeminder-goal-template-fields-alist)
14981498+ (eval (cdr it) `((goal . ,goal) t))
14991499+ (highlight-subtly (format "%s" (cdr (assoc sexp goal))))))
15001500+ (t (eval sexp))))))))
15011501+15021502+(define-derived-mode beeminder-goal-mode special-mode "Beeminder goal"
15031503+ "A major mode for a buffer displaying details of a Beeminder goal,
15041504+in particular the history of datapoints.")
15051505+15061506+(defun beeminder-next-datapoint (count)
15071507+ "Move forward COUNT datapoints."
15081508+ (interactive "p")
15091509+ (forward-line 1)
15101510+ (re-search-forward "^[0-9a-f]\\{24\\}" nil t count)
15111511+ (beginning-of-line))
15121512+15131513+(defun beeminder-previous-datapoint (count)
15141514+ "Move forward COUNT datapoints."
15151515+ (interactive "p")
15161516+ (beginning-of-line)
15171517+ (re-search-backward "^[0-9a-f]\\{24\\}" nil t count))
15181518+15191519+(define-key beeminder-goal-mode-map (kbd "n") #'beeminder-next-datapoint)
15201520+(define-key beeminder-goal-mode-map (kbd "p") #'beeminder-previous-datapoint)
15211521+15221522+(defface subtle-highlight '((t :foreground "#004400"))
15231523+ "Face for subtly highlighting things.")
15241524+15251525+(defun highlight-subtly (string)
15261526+ "Make STRING stand out, but only a little."
15271527+ (propertize string 'face 'subtle-highlight))
15281528+15291529+(defun number-to-human-string (number &optional width)
15301530+ "Convert NUMBER to a human-friendly form, at least WIDTH characters.
15311531+If NUMBER is greater than 10, use one decimal place. Otherwise,
15321532+use two. Trim any non-significant trailing zeros and the decimal
15331533+point if needed."
15341534+ (let ((str (replace-regexp-in-string
15351535+ "\\.?0+$" ""
15361536+ (format (cond
15371537+ ((> number 10) "%.1f")
15381538+ (t "%.2f"))
15391539+ number))))
15401540+ (if width (format (format "%%%ds" width) str) str)))
15411541+15421542+(defun beeminder-display-midnight-setting (seconds)
15431543+ "Convert SECONDS to or from midnight to a time string."
15441544+ (when (< seconds 0) (setq seconds (+ seconds (* 24 60 60))))
15451545+ (let* ((hours (/ seconds 60 60))
15461546+ (minutes (/ (- seconds (* hours 60 60)) 60)))
15471547+ (format "%d:%02d" hours minutes)))
15481548+15491549+(defun beeminder-display-goal-type (goaltype)
15501550+ "Convert GOALTYPE to a printed representation."
15511551+ (cl-case (intern goaltype)
15521552+ (hustler "do more")
15531553+ (biker "odometer")
15541554+ (fatloser "weight loss")
15551555+ (gainer "gain weight")
15561556+ (inboxer "inbox fewer")
15571557+ (drinker "do less")
15581558+ (custom "custom")))
15591559+15601560+(defcustom beeminder-goal-template
15611561+ "Details for Beeminder goal #slug for user #username#backburnerp
15621562+#title#dirtyp
15631563+goal target: #target on #goaldate at rate #rate per #runit (currently at #curval, done today: #donetoday)
15641564+goal type: #goaltype#autodatap
15651565+safe until #losedate (current pledge: #pledge, left to do: #limsum, midnight setting: #midnight)
15661566+15671567+Recent datapoints (#history-length days):
15681568+#datapoints
15691569+"
15701570+ "The default template for displaying goal details.
15711571+See the docstring of the function
15721572+`beeminder-insert-goal-template-with-expansion' for the list of
15731573+available keywords.")
15741574+15751575+(defun beeminder-refresh-goal-details ()
15761576+ "Refresh goal details, assuming that the respective buffer
15771577+exists and is set up properly."
15781578+ (let ((inhibit-read-only t))
15791579+ (erase-buffer)
15801580+ (beeminder-insert-goal-template-with-expansion
15811581+ beeminder-goal-template
15821582+ beeminder-detailed-goal))
15831583+ (goto-char (point-min)))
15841584+15851585+(defvar beeminder-detailed-goal nil
15861586+ "The current goal in the details buffer.")
15871587+15881588+(defun beeminder-display-goal-details (goal)
15891589+ "Display details about GOAL in a temporary buffer."
15901590+ (interactive (list (current-or-read-goal)))
15911591+ (pop-to-buffer "*Beeminder goal details*")
15921592+ (remove-images (point-min) (point-max))
15931593+ (beeminder-goal-mode)
15941594+ (setq-local beeminder-detailed-goal goal)
15951595+ (beeminder-refresh-goal-details))
15961596+15971597+(define-key beeminder-mode-map (kbd "TAB") #'beeminder-display-goal-details)
15981598+15991599+(defcustom beeminder-confirm-datapoint-deletion #'y-or-n-p
16001600+ "How to ask for confirmation of datapoint deletion.
16011601+If nil, don't ask."
16021602+ :type '(choice (const :tag "Ask with yes-or-no-p" yes-or-no-p)
16031603+ (const :tag "Ask with y-or-n-p" y-or-n-p)
16041604+ (const :tag "Don't ask at all" nil)
16051605+ (funtion :tag "Predicate function"))
16061606+ :group 'beeminder)
16071607+16081608+(defun beeminder-get-datapoint-id ()
16091609+ "Return the id of the datapoint at point."
16101610+ (save-excursion
16111611+ (beginning-of-line)
16121612+ (if (looking-at "[0-9a-f]\\{24\\}")
16131613+ (match-string-no-properties 0)
16141614+ (error "Not at a datapoint"))))
16151615+16161616+(defun beeminder-delete-datapoint (id)
16171617+ "Delete datapoint with id ID.
16181618+If called interactively, take the id from the beginning of the
16191619+line."
16201620+ (interactive (list (beeminder-get-datapoint-id)))
16211621+ (if (and beeminder-confirm-datapoint-deletion
16221622+ (funcall beeminder-confirm-datapoint-deletion "Are you sure you want to delete this datapoint?"))
16231623+ (beeminder-request-delete
16241624+ (concat "/goals/" (cdr (assoc 'slug beeminder-detailed-goal)) "/datapoints/" id ".json")
16251625+ (cl-function (lambda (&rest _)
16261626+ (let ((datapoints (cdr (assoc 'datapoints beeminder-detailed-goal))))
16271627+ (beeminder-inc-alist-value 'donetoday
16281628+ beeminder-detailed-goal
16291629+ (- (cdr (assoc 'value (cl-find id datapoints
16301630+ :key (lambda (dp)
16311631+ (cdr (assoc 'id dp)))
16321632+ :test #'string=)))))
16331633+ (cl-delete id datapoints :key (lambda (dp) (cdr (assoc 'id dp))) :test #'string=)
16341634+ (beeminder-make-goal-dirty (beeminder-get-slug beeminder-detailed-goal))
16351635+ (beeminder-refresh-goal-details)
16361636+ (beeminder-log (format "datapoint %s succesfully deleted" id)))))
16371637+ (cl-function (lambda (&key error-thrown &allow-other-keys)
16381638+ (beeminder-log (format "error while deleting datapoint %s for goal %s: %s"
16391639+ id
16401640+ (cdr (assoc 'slug beeminder-detailed-goal))
16411641+ error-thrown)
16421642+ :error))))))
16431643+16441644+(define-key beeminder-goal-mode-map (kbd "d") #'beeminder-delete-datapoint)
16451645+16461646+(defun beeminder-get-datapoint (id datapoints)
16471647+ "Return datapoint with id ID from DATAPOINTS."
16481648+ (cl-find id datapoints
16491649+ :key (lambda (dp)
16501650+ (cdr (assoc 'id dp)))
16511651+ :test #'string=))
16521652+16531653+(defun beeminder-edit-datapoint (id)
16541654+ "Edit datapoint with id ID.
16551655+If called interactively, take the id from the beginning of the
16561656+line."
16571657+ (interactive (list (beeminder-get-datapoint-id)))
16581658+ (let* ((datapoints (reverse (cdr (assoc 'datapoints beeminder-detailed-goal))))
16591659+ (datapoint (aif (beeminder-get-datapoint id datapoints)
16601660+ it
16611661+ (error "%s" "Invalid datapoint id -- beeminder-get-datapoint")))
16621662+ (timestamp (ask-for-timestamp (cdr (assoc 'timestamp datapoint))))
16631663+ (value (string-to-number
16641664+ (let ((default-value (cdr (assoc 'value datapoint))))
16651665+ (read-string (format "Value (default %s): " default-value)
16661666+ nil nil (number-to-string default-value)))))
16671667+ (comment (let ((comment-history
16681668+ (mapcar (lambda (dp)
16691669+ (cdr (assoc 'comment dp)))
16701670+ datapoints)))
16711671+ (read-string "Comment: "
16721672+ (cdr (assoc 'comment datapoint))
16731673+ (cons 'comment-history
16741674+ (1+ (position
16751675+ id
16761676+ datapoints
16771677+ :key (lambda (dp)
16781678+ (cdr (assoc 'id dp)))
16791679+ :test #'string=)))
16801680+ nil t))))
16811681+ (beeminder-log "updating datapoint...")
16821682+ (beeminder-request-put (format "/goals/%s/datapoints/%s.json"
16831683+ (beeminder-get-slug beeminder-detailed-goal)
16841684+ id)
16851685+ (list
16861686+ (cons "value" (format "%s" value))
16871687+ (cons "comment" comment)
16881688+ (cons "timestamp" (format "%s" timestamp)))
16891689+ (cl-function (lambda (&rest _) (beeminder-log "updating datapoint...done")))
16901690+ (cl-function (lambda (&rest _) (beeminder-log "updating datapoint failed!" :error))))))
16911691+16921692+(define-key beeminder-goal-mode-map (kbd "e") #'beeminder-edit-datapoint)
16931693+16941694+(defun beeminder-display-raw-goal-details ()
16951695+ "Display the raw details about GOAL in a temporary buffer.
16961696+The internal representation is an alist."
16971697+ (interactive)
16981698+ (let ((goal beeminder-detailed-goal))
16991699+ (pop-to-buffer "*Beeminder raw goal details*")
17001700+ (let ((inhibit-read-only t))
17011701+ (erase-buffer)
17021702+ (insert (pp-to-string goal))
17031703+ (goto-char (point-min))
17041704+ (special-mode))))
17051705+17061706+(define-key beeminder-goal-mode-map (kbd ".") #'beeminder-display-raw-goal-details)
17071707+17081708+(defun beeminder-view-in-browser (goal)
17091709+ "View GOAL in the web browser."
17101710+ (interactive (list (or beeminder-detailed-goal (current-or-read-goal))))
17111711+ (browse-url (format "https://beeminder.com/%s/%s" beeminder-username (beeminder-alist-get 'slug goal))))
17121712+17131713+(define-key beeminder-mode-map (kbd "W") #'beeminder-view-in-browser)
17141714+(define-key beeminder-goal-mode-map (kbd "W") #'beeminder-view-in-browser)
17151715+17161716+17171717+;; Downloading more datapoints
17181718+(defun beeminder-download-datapoints (slug-str days)
17191719+ "Download datapoints for goal named SLUG-STR from last DAYS.
17201720+If called interactively in the *Beeminder goal details* buffer, use
17211721+current goal; otherwise, ask for the goal. If called without a prefix
17221722+argument, increase the downloaded history by
17231723+`beeminder-history-length' days."
17241724+ (interactive (list (if beeminder-detailed-goal
17251725+ (cdr (assoc 'slug beeminder-detailed-goal))
17261726+ (cdr (assoc 'slug (current-or-read-goal))))
17271727+ current-prefix-arg))
17281728+ (setq days
17291729+ (if days
17301730+ (prefix-numeric-value days)
17311731+ beeminder-history-length))
17321732+ (setq days
17331733+ (cond ((> days 0)
17341734+ (+ (cdr (assoc 'history-length (beeminder-slug-to-goal (intern slug-str))))
17351735+ days))
17361736+ ((< days 0)
17371737+ (- days))
17381738+ (t 0)))
17391739+ (beeminder-log (format "fetching datapoints for goal %s..." slug-str))
17401740+ (beeminder-request-get (format "/goals/%s.json" slug-str)
17411741+ (cons (cons "datapoints" "true")
17421742+ (unless (zerop days)
17431743+ (cons (cons "diff_since"
17441744+ (number-to-string
17451745+ (- (last-user-midnight (beeminder-current-time))
17461746+ (* days 24 60 60))))
17471747+ nil)))
17481748+ (cl-function (lambda (&key data &allow-other-keys)
17491749+ (beeminder-log (format "fetching datapoints for goal %s...done" slug-str))
17501750+ (let* ((gl (beeminder-slug-to-goal (intern slug-str)))
17511751+ (dp (assoc 'datapoints gl))
17521752+ (hl (assoc 'history-length gl)))
17531753+ (setcdr dp (beeminder-to-list (cdr (assoc 'datapoints data))))
17541754+ (setcdr hl days)
17551755+ (when beeminder-detailed-goal
17561756+ (beeminder-refresh-goal-details)))))
17571757+ (cl-function (lambda (&key error-thrown &allow-other-keys)
17581758+ (beeminder-log (format "fetching datapoints for goal %s...error: %s" slug-str error-thrown))))))
17591759+17601760+(define-key beeminder-goal-mode-map (kbd "m") #'beeminder-download-datapoints)
17611761+17621762+17631763+;; Statistics
17641764+(defun beeminder-determine-date (time day-end)
17651765+ "Return date for TIME, taking DAY-END into account.
17661766+TIME is the number of seconds counted from the beginning of Unix
17671767+epoch; DAY-END is the offset from midnight in seconds. The date
17681768+is a string in ISO 8601 basic format (i.e., \"20160417\" for
17691769+April 17, 2016)."
17701770+ (format-time-string "%Y%m%d" (time-subtract time day-end)))
17711771+17721772+(defun beeminder-gather-datapoints-by-day (goal)
17731773+ "Return an alist of datapoints, collated by date.
17741774+The car of each entry is a string representing the date in ISO
17751775+8601 basic format (i.e., \"20160417\" for April 17, 2016), and
17761776+the cdr is the list of datapoints. If
17771777+`beeminder-use-goal-midnight-today-values' is nil, use the goal's
17781778+\"midnight\" setting to determine the date; otherwise, use
17791779+`beeminder-when-the-day-ends'."
17801780+ (let ((day-end (if beeminder-use-goal-midnight-today-values
17811781+ beeminder-when-the-day-ends
17821782+ (cdr (assoc 'deadline goal))))
17831783+ datapoints-by-day)
17841784+ (mapc (lambda (datapoint)
17851785+ (let ((date (beeminder-determine-date (cdr (assoc 'timestamp datapoint)) day-end)))
17861786+ (aif (assoc date datapoints-by-day)
17871787+ (push datapoint (cdr it))
17881788+ (push (list date datapoint) datapoints-by-day))))
17891789+ (cdr (assoc 'datapoints goal)))
17901790+ datapoints-by-day))
17911791+17921792+;; (defun beeminder-gather-datapoints-by-day (goal)
17931793+;; "Return an alist of datapoints, collated by date.
17941794+;; The car of each entry is a string representing the date in ISO
17951795+;; 8601 basic format (i.e., \"20160417\" for April 17, 2016), and
17961796+;; the cdr is the list of datapoints. If
17971797+;; `beeminder-use-goal-midnight-today-values' is nil, use the goal's
17981798+;; \"midnight\" setting to determine the date; otherwise, use
17991799+;; `beeminder-when-the-day-ends'."
18001800+;; (let ((day-end (if beeminder-use-goal-midnight-today-values
18011801+;; beeminder-when-the-day-ends
18021802+;; (cdr (assoc 'deadline goal))))
18031803+;; datapoints-by-day)
18041804+;; (mapc (lambda (datapoint)
18051805+;; (let ((date (beeminder-determine-date (cdr (assoc 'timestamp datapoint)) day-end)))
18061806+;; (aif (assoc date datapoints-by-day)
18071807+;; (push datapoint (cdr it))
18081808+;; (push (list date datapoint) datapoints-by-day))))
18091809+;; (cdr (assoc 'datapoints goal)))
18101810+;; datapoints-by-day))
18111811+18121812+(defun beeminder-uniq-mean (list)
18131813+ "Return the mean of numbers in LIST after deleting
18141814+ duplicates."
18151815+ (let ((list list))
18161816+ (/ (apply #'+ (delete-dups list))
18171817+ (length list)
18181818+ 1.0)))
18191819+18201820+(defun beeminder-median (list)
18211821+ "Return a median of numbers in LIST.
18221822+If LIST contains an even number of elements n, return
18231823+the (n/2)-th one."
18241824+ (let ((list list) median-list)
18251825+ (setq list (sort list #'<)
18261826+ median-list list)
18271827+ (while (cddr list)
18281828+ (setq median-list (cdr median-list)
18291829+ list (cddr list)))
18301830+ (if (cdr list)
18311831+ (* 0.5 (+ (car median-list) (cadr median-list)))
18321832+ (car median-list))))
18331833+18341834+(defvar beeminder-aggregation-methods
18351835+ '(("sum" . (lambda (dps) (apply #'+ dps)))
18361836+ ("last" . car) ; this is no mistake - the list is in reverse order!
18371837+ ("first" . (lambda (dps) (car (last dps))))
18381838+ ("min" . (lambda (dps) (apply #'min dps)))
18391839+ ("max" . (lambda (dps) (apply #'max dps)))
18401840+ ("truemean" . (lambda (dps) (/ (apply #'+ dps) (length dps) 1.0)))
18411841+ ("uniqmean" . beeminder-uniq-mean)
18421842+ ("mean" . beeminder-uniq-mean)
18431843+ ("median" . beeminder-median)
18441844+ ("jolly" . (lambda (dps) (if dps 1 0)))
18451845+ ("binary" . (lambda (dps) (if dps 1 0)))
18461846+ ("nonzero" . (lambda (dps) (if (cl-some (lambda (dp) (not (= 0 dp))) dps) 1 0)))
18471847+ ("triangle" . (lambda (dps) (let ((sum (apply #'+ dps))) (* sum (1+ sum) 0.5))))
18481848+ ("square" . (lambda (dps) (let ((sum (apply #'+ dps))) (* sum sum))))
18491849+ ("count" . length))
18501850+ "An alist mapping aggregation methods to actual functions.")
18511851+18521852+(defun beeminder-aggregate-values (values aggday)
18531853+ "Aggregate VALUES (from one day) using the AGGDAY method."
18541854+ (funcall (beeminder-alist-get aggday beeminder-aggregation-methods)
18551855+ values))
18561856+18571857+(defun beeminder-aggregate-datapoints (datapoints-by-day aggday)
18581858+ "Aggregate DATAPOINTS-BY-DAY using the AGGDAY method."
18591859+ (mapcar (lambda (day-datapoints)
18601860+ (cons (car day-datapoints)
18611861+ (beeminder-aggregate-values
18621862+ (mapcar (lambda (dp) (beeminder-alist-get 'value dp))
18631863+ (cdr day-datapoints))
18641864+ aggday)))
18651865+ datapoints-by-day))
18661866+18671867+18681868+;; Displaying graphs
18691869+18701870+(defun beeminder-download-graph (slug-str)
18711871+ "Download graph for goal SLUG and put it in the tmp directory,
18721872+under the \"beeminder-el\" subdirectory and filename
18731873+\"SLUG.png\". Return the full filename."
18741874+ (make-directory (concat temporary-file-directory "beeminder-el") t)
18751875+ (let* ((image-file (concat temporary-file-directory "beeminder-el/" slug-str ".png"))
18761876+ (inhibit-message t))
18771877+ (url-copy-file (beeminder-alist-get 'graph_url (beeminder-slug-to-goal (intern slug-str)))
18781878+ image-file
18791879+ t)
18801880+ image-file))
18811881+18821882+(defun beeminder-insert-graph (image)
18831883+ "Insert IMAGE at end-of-buffer. Remove any existing images
18841884+first and position the point and window so that the image can be
18851885+seen next."
18861886+ (remove-images (point-min) (point-max))
18871887+ (goto-char (point-max))
18881888+ (let ((inhibit-read-only t))
18891889+ (remove-images (point-min) (point-max))
18901890+ (save-excursion
18911891+ (insert "\n")
18921892+ (put-image image (point-max) "[Graphs are not supported in this Emacs!]")))
18931893+ (let* ((size (image-size image))
18941894+ (width (ceiling (car size)))
18951895+ (height (ceiling (cdr size))))
18961896+ (fit-window-to-buffer (selected-window) height height width width))
18971897+ (recenter 0))
18981898+18991899+(defun beeminder-display-graph (slug-str)
19001900+ "Download the graph of the goal SLUG-STR and display it.
19011901+Switch to the \"details\" buffer first if needed. Do nothing if
19021902+the graph is already displayed."
19031903+ (interactive (list (if beeminder-detailed-goal
19041904+ (cdr (assoc 'slug beeminder-detailed-goal))
19051905+ (cdr (assoc 'slug (current-or-read-goal))))))
19061906+ (let* ((image-file (beeminder-download-graph slug-str))
19071907+ (image (create-image image-file)))
19081908+ (beeminder-display-goal-details (beeminder-slug-to-goal (intern slug-str)))
19091909+ (beeminder-insert-graph image)))
19101910+19111911+(define-key beeminder-goal-mode-map (kbd "i") #'beeminder-display-graph)
19121912+(define-key beeminder-mode-map (kbd "i") #'beeminder-display-graph)
19131913+19141914+19151915+;; Org-mode integration
19161916+19171917+(defcustom beeminder-org-inherit-beeminder-properties nil
19181918+ "Make beeminder.el use property inheritance.")
19191919+19201920+(defcustom beeminder-org-default-comment "%h at %t"
19211921+ "Default format of the comment")
19221922+19231923+(defun beeminder-org-string-substitute (string)
19241924+ "Substitute strings for percent-sign codes in STRING.
19251925+Codes are: `%t' - current time, `%h' - current headline, `%p' -
19261926+current path, `%%' - percent sign."
19271927+ (let ((time (beeminder-current-time-string))
19281928+ (headline (substring-no-properties (org-get-heading t t)))
19291929+ (path (mapconcat #'identity (org-get-outline-path t) "/")))
19301930+ (format-spec string
19311931+ `((?% . "%")
19321932+ (?t . ,time)
19331933+ (?h . ,headline)
19341934+ (?p . ,path)))))
19351935+19361936+(defun beeminder-org-generate-comment ()
19371937+ "Given the comment property, generate the comment text. Assume
19381938+that the point is in the right place."
19391939+ (let ((comment-prop
19401940+ (or (org-entry-get (point)
19411941+ "comment"
19421942+ beeminder-org-inherit-beeminder-properties)
19431943+ beeminder-org-default-comment)))
19441944+ (cond
19451945+ ((string= comment-prop "time")
19461946+ (concat "via Org-mode at " (beeminder-current-time-string)))
19471947+ ((string= comment-prop "ask")
19481948+ nil)
19491949+ ((or (string= comment-prop "headline")
19501950+ (null comment-prop))
19511951+ (substring-no-properties (org-get-heading t t)))
19521952+ ((string= comment-prop "path")
19531953+ (mapconcat #'identity (org-get-outline-path t) "/"))
19541954+ (t (beeminder-org-string-substitute comment-prop)))))
19551955+19561956+(defun beeminder-org-submit-on-done (state-change)
19571957+ "Submit a datapoint when marking an item as DONE.
19581958+This function should be placed in `org-trigger-hook'. It looks
19591959+up the following properties of the headline: the \"beeminder\"
19601960+property (which should be set to \"done\", the \"slug\"
19611961+property (which should be set to the slug of the goal), the
19621962+\"amount\" property (defaults to 1), the \"ask-comment\"
19631963+property (asks for the comment if it is present)."
19641964+ (let ((position (plist-get state-change :position)))
19651965+ (if (and (string= (downcase (or (org-entry-get position
19661966+ "beeminder"
19671967+ beeminder-org-inherit-beeminder-properties)
19681968+ ""))
19691969+ "done")
19701970+ (eq (plist-get state-change :type)
19711971+ 'todo-state-change)
19721972+ (member (plist-get state-change :to)
19731973+ org-done-keywords))
19741974+ (let* ((slug-str (org-entry-get position
19751975+ "slug"
19761976+ beeminder-org-inherit-beeminder-properties))
19771977+ (amount (aif (org-entry-get position
19781978+ "amount"
19791979+ beeminder-org-inherit-beeminder-properties)
19801980+ (string-to-number it)
19811981+ 1))
19821982+ (comment (beeminder-org-generate-comment)))
19831983+ (beeminder-submit-datapoint slug-str amount comment)))))
19841984+19851985+(defun beeminder-org-submit-clock-at-point ()
19861986+ "Submit the data from the clock item at point to Beeminder.
19871987+This is mainly useful if submitting on clocking out (see
19881988+`beeminder-org-submit-on-clock-out' failed for some reason, so
19891989+that the user may want to submit clock items later."
19901990+ (interactive)
19911991+ (let ((element (org-element-at-point)))
19921992+ (if (eq (org-element-type element) 'clock)
19931993+ (let ((timestamp (org-element-property :value element))
19941994+ (duration (org-element-property :duration element)))
19951995+ (when (string-match "\\([[:digit:]]+\\):\\([[:digit:]]\\{2\\}\\)" duration)
19961996+ (let* ((minutes (+ (* 60 (string-to-number (match-string 1 duration)))
19971997+ (string-to-number (match-string 2 duration))))
19981998+ (slug-str (org-entry-get (point)
19991999+ "slug"
20002000+ beeminder-org-inherit-beeminder-properties))
20012001+ (comment (beeminder-org-generate-comment))
20022002+ (multiplier (cl-case (intern (or (org-entry-get (point)
20032003+ "unit"
20042004+ beeminder-org-inherit-beeminder-properties)
20052005+ ""))
20062006+ ((hour hours)
20072007+ (/ 1 60.0))
20082008+ ((hail-Mary hail-Marys)
20092009+ 3)
20102010+ ; 1 hail-Mary ≈ 20 seconds
20112011+ (t 1)))
20122012+ (year-end (org-element-property :year-end timestamp))
20132013+ (month-end (org-element-property :month-end timestamp))
20142014+ (day-end (org-element-property :day-end timestamp))
20152015+ (hour-end (org-element-property :hour-end timestamp))
20162016+ (minute-end (org-element-property :minute-end timestamp))
20172017+ (id (format "%04d%02d%02d%02d%02dto%04d%02d%02d%02d%02d"
20182018+ (org-element-property :year-start timestamp)
20192019+ (org-element-property :month-start timestamp)
20202020+ (org-element-property :day-start timestamp)
20212021+ (org-element-property :hour-start timestamp)
20222022+ (org-element-property :minute-start timestamp)
20232023+ year-end
20242024+ month-end
20252025+ day-end
20262026+ hour-end
20272027+ minute-end))
20282028+ (timestamp (time-to-seconds (encode-time
20292029+ 0
20302030+ minute-end
20312031+ hour-end
20322032+ day-end
20332033+ month-end
20342034+ year-end))))
20352035+ (beeminder-submit-datapoint slug-str (* minutes multiplier)
20362036+ comment
20372037+ timestamp
20382038+ id))))
20392039+ (beeminder-log "no clock at point!" :nolog))))
20402040+20412041+(defcustom beeminder-org-submit-all-clocks-default-minutes (* 24 60)
20422042+ "By default, only the clocks from this many lastminutes will be
20432043+submitted by `beeminder-org-submit-all-clocks'. Does not have to
20442044+be an integer (i.e., value like 0.5 means 30 seconds).")
20452045+20462046+(defun beeminder-org-submit-all-clocks (begin end minutes)
20472047+ "Submit all clocks from last MINUTES in the region to Beeminder.
20482048+In interactive use, use region if active and current subtree
20492049+otherwise. Use with caution!"
20502050+ (interactive (if (use-region-p)
20512051+ (list (region-beginning)
20522052+ (region-end)
20532053+ current-prefix-arg)
20542054+ (list nil nil current-prefix-arg)))
20552055+ (unless (numberp minutes)
20562056+ (setq minutes beeminder-org-submit-all-clocks-default-minutes))
20572057+ (save-excursion
20582058+ (save-restriction
20592059+ (narrow-to-region
20602060+ (or begin (progn (org-back-to-heading t)
20612061+ (point)))
20622062+ (or end (progn (org-end-of-subtree t t)
20632063+ (when (and (org-at-heading-p) ; see org-narrow-to-subtree
20642064+ (not (eobp)))
20652065+ (backward-char 1))
20662066+ (point))))
20672067+ (goto-char (point-min))
20682068+ (while (re-search-forward "CLOCK: " nil t)
20692069+ (let ((eap (org-element-at-point)))
20702070+ (when (and (eq (org-element-type eap)
20712071+ 'clock)
20722072+ (eq (org-element-property :status eap)
20732073+ 'closed))
20742074+ (let ((ts (org-element-property :value eap)))
20752075+ (when (< (time-to-seconds
20762076+ (time-subtract (beeminder-current-time)
20772077+ (encode-time 0
20782078+ (org-element-property :minute-end ts)
20792079+ (org-element-property :hour-end ts)
20802080+ (org-element-property :day-end ts)
20812081+ (org-element-property :month-end ts)
20822082+ (org-element-property :year-end ts))))
20832083+ (* 60 minutes))
20842084+ (beeminder-org-submit-clock-at-point)))))))))
20852085+20862086+(defun beeminder-org-submit-on-clock-out ()
20872087+ "Submit the time clocked for this item.
20882088+This function should be placed in `org-clock-out-hook'. It looks
20892089+up the following properties of the headline: the \"beeminder\"
20902090+property (which should be set to \"clock\", the \"slug\"
20912091+property (which should be set to the slug of the goal), the
20922092+\"unit\" property (which may be \"minutes\", which is the
20932093+default, or \"hours\", etc.\"), the \"ask-comment\"
20942094+property (asks for the comment if it is present)."
20952095+ (when (and (string= (downcase (or (org-entry-get (point)
20962096+ "beeminder"
20972097+ beeminder-org-inherit-beeminder-properties)
20982098+ ""))
20992099+ "clock")
21002100+ (not (string-match " LINE REMOVED$" (or (current-message) ""))) ; this is really hackish
21012101+ (org-entry-get
21022102+ (point) "slug" beeminder-org-inherit-beeminder-properties))
21032103+ (beeminder-org-submit-clock-at-point)))
21042104+21052105+(define-minor-mode beeminder-org-integration-mode
21062106+ "Toggle a (global) minor mode for Org/Beeminder integration.
21072107+When on, clocking out and marking as DONE for headlines with suitable
21082108+:beeminder: property is submitted automatically."
21092109+ :init-value nil
21102110+ :global t
21112111+ :lighter " B-O"
21122112+ (if beeminder-org-integration-mode
21132113+ (progn
21142114+ (add-hook 'org-trigger-hook #'beeminder-org-submit-on-done)
21152115+ (add-hook 'org-clock-out-hook #'beeminder-org-submit-on-clock-out))
21162116+ (remove-hook 'org-trigger-hook #'beeminder-org-submit-on-done)
21172117+ (remove-hook 'org-clock-out-hook #'beeminder-org-submit-on-clock-out)))
21182118+21192119+21202120+(provide 'beeminder)
21212121+21222122+21232123+;;; beeminder.el ends here
+6
spacemacs-private/snippets/README.md
···11+# Private directory for Yasnippets snippets
22+33+The content of this directory is ignored by Git. This is the default place
44+where to store your private yasnippets.
55+66+This path will be loaded automatically and used whenever Yasnippets loads.
···11+# Private directory for Yatemplate templates
22+33+The content of this directory is ignored by Git. This is the default place
44+where to store your private templates.
55+66+This path will be loaded automatically and used whenever Yatemplate loads.