Arranging Emacs windows

posted Nov 3, 2019, 3:17 AM by Juan Jose Garcia-Ripoll   [ updated Nov 3, 2019, 3:18 AM ]

Dealing with windows in Emacs can be a bit annoying, until someone explains you the philosophy behind it and you dig a bit into the manual for display-buffer and display-buffer-alist. Even then, the problem that package managers do not follow the Emacs canon, or that this has changed a bit over time, makes it difficult to configure some pieces of software.

I leave here my customizations as of Nov. 2019, in case they may help someone. They are part of an init.org file that is converted into an init.el automatically when saving it, but it should be clear as standalone explanation.

First, we add some functions that I will use to select buffers based on modes. This is not usual: I just need it for buffers whose name does not really follow any rule; otherwise the method from display-buffer-alist just works.
#+begin_src emacs-lisp :lexical t :tangle init.el
  (defun jjgr-dedicated-window (fun)
    (lambda (buffer &optional alist)
      (let ((window (funcall fun buffer alist)))
        (when window
          (set-window-dedicated-p window t))
        window)))

  (defun jjgr-rx-mode-name (mode-regexp)
    (lambda (buffer &rest optional)
      (with-current-buffer buffer
        (string-match mode-regexp mode-name))))
#+end_src

Then I do the configuration itself. I have basically three scenarios where I use Emacs: email, coding and writing. I also have three sets of windows: the ones I open (my files, a new shell), temporary windows (captures, new emails), and informative windows (logs, error messages, help windows).
#+begin_src emacs-lisp :lexical t :tangle init.el
  (setq
   ;; Kill a frame when quitting its only window
   frame-auto-hide-function 'delete-frame
   ;; Maximum number of side-windows to create on (left top right bottom)
   window-sides-slots '(0 1 1 1)
   ;; Default rules
   display-buffer-alist
   `(;; Display *Help* buffer at the bottom-most slot
     ("*\\(Help\\|trace-\\|Backtrace\\|RefTeX.*\\)"
      (display-buffer-reuse-window display-buffer-in-previous-window display-buffer-in-side-window)
      (side . right)
      (slot . 0)
      (window-width . 0.33)
      (reusable-frames . visible))
     ("^\\*info"
      (display-buffer-reuse-window display-buffer-in-previous-window display-buffer-pop-up-frame)
      (pop-up-frame-parameters
        (width . 80)
        (left . 1.0)
        (fullscreen . fullheight)))
     ;; Open new edited messages in a right-hand frame
     ;; For this to close the frame, add
     ;; (add-hook 'wl-draft-kill-pre-hook 'quit-window)
     ("\\(\\*draft\\*\\|Draft/\\)"
      (display-buffer-reuse-window display-buffer-in-previous-window display-buffer-pop-up-frame)
      (pop-up-frame-parameters
        (width . 80)
        (left . 1.0)
        (fullscreen . fullheight)))
     ;; TeX output buffers to bottom, with 10 lines
     (,(jjgr-rx-mode-name "^\\(TeX Output\\|TeX\\)")
      (display-buffer-reuse-window display-buffer-in-previous-window display-buffer-in-side-window)
      (side . bottom)
      (slot . 0)
      (window-height . 10)
      (reusable-frames . visible))
     ;; Display *BBDB* buffer on the bottom frame
     ("*BBDB"
      (display-buffer-reuse-window display-buffer-in-previous-window display-buffer-in-side-window)
      (side . bottom)
      (slot . 0)
      (window-height . 10)
      (reusable-frames . visible))
     ;; Split shells at the bottom
     ("^\\*[e]shell"
      (display-buffer-reuse-window display-buffer-in-previous-window display-buffer-below-selected)
      (window-min-height . 20)
      (reusable-frames . visible)
      )
     )
   )
#+end_src

I find the syntax of display-buffer-alist rather self explanatory. Helper buffers, I tend to place on a side-window that pops to the right. The exception are long *info* buffers with Emacs manuals, that I like to read on their own frame (i.e. window from the operating system). The database of contacts, *BBDB*, pops at the bottom of the screen in a small window with ten lines. Shells are split from the current window, at the bottom, instead of overriding the content of the window (the default).

Note how I have to modify how Wanderlust (the email client) works. I instruct  Emacs to open email editing buffers on a new operating system window (frame). However I need to use quit-window to once the draft is finished or abandoned, because otherwise the frame will not be closed automatically. This is because Wanderlust uses delete-window instead.

Managing bibliographies from Emacs (v2)

posted Oct 24, 2019, 10:29 AM by Juan Jose Garcia-Ripoll   [ updated Oct 26, 2019, 11:33 AM ]

This is a tentative blog post about how I am migrating from Mendely to using bibtex files in Emacs, combined with some annotation goodies to be added later on.

My current backend for managing bibliographies is ebib, a rather sleek piece of software to query and reference bibtex databases, either individually, or grouped together.

To install ebib, assuming you already have use-package, add the following lines to your .emacs file
(use-package ebib
  :defer t
  :ensure t
  :custom
  (ebib-preload-bib-files '("bibtexfile1.bib" "bibtexfile2.bib"))
  (ebib-bib-search-dirs '("/default/path/to/bibtex/files")))
This downloads the library from your default repository, and configures it to use some default bibtex files.

A nice complement to ebib is biblio, a library to query various databases, such as Crossref, the arXiv, etc. This library allows you to grab bibtex records for the articles you search for. The following code links this library with ebib, so that you can press Ctrl-i and the current record is added to your ebib database.
(use-package biblio
  :defer t
  :ensure t
  :custom
  (biblio-arxiv-bibtex-header "misc")
  :bind
  (:map biblio-selection-mode-map
        ("C-i" . biblio-to-ebib-and-quit))
  :init
  ;; This links biblio to ebib
  (defun biblio-to-ebib-and-quit ()
    (interactive)
    (biblio--selection-forward-bibtex
     (lambda (entry metadata)
       (print entry)
       (ebib-capture-raw-bibtex entry)
       (ebib--update-buffers))
     t))
  )

In order for this to work, you need the following code, which extends ebib with a function to edit raw Bibtex entries from arbitrary sources
(defvar ebib-capture-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\C-c\C-c" #'ebib-capture-finalize)
    (define-key map "\C-c\C-k" #'ebib-capture-kill)
    map)
  "Keymap for `ebib-capture-mode', a minor mode.
  Use this map to set additional keybindings for when Org mode is used
  for a capture buffer.")

(defvar ebib-capture-mode-hook nil
  "Hook for the `ebib-capture-mode' minor mode.")

(define-minor-mode ebib-capture-mode
  "Minor mode for special key bindings in a capture buffer.

  Turning on this mode runs the normal hook `ebib-capture-mode-hook'."
  nil " Cap" ebib-capture-mode-map
  (setq-local
   header-line-format
   (substitute-command-keys
    "\\<ebib-capture-mode-map>Capture buffer.  Finish \
  `\\[ebib-capture-finalize]', abort `\\[ebib-capture-kill]'.")))

(defun ebib-capture-kill ()
  "Abort the current capture process."
  (interactive)
  ;; FIXME: This does not do the right thing, we need to remove the
  ;; new stuff by hand it is easy: undo, then kill the buffer
  (kill-buffer (current-buffer))
  (quit-window))

(defun ebib-capture-finalize ()
  "Save entries and finalize."
  (interactive)
  (ebib--execute-when
    ((or slave-db filtered-db)
     (error "[Ebib] Cannot merge into a filtered or a slave database"))
    (real-db
     (unless (ebib-capture-check-duplicates)
       (let ((result (ebib--bib-find-bibtex-entries ebib--cur-db nil)))
         (ebib--log 'message "%d entries, %d @Strings and %s @Preamble found in file."
                    (car result)
                    (cadr result)
                    (if (nth 2 result) "a" "no")))
       (ebib--update-buffers)
       (ebib--set-modified t ebib--cur-db)
       (ebib-capture-kill)))
    (default (beep))))

(defun ebib-capture-check-duplicates ()
  (interactive)
  (let ((db (ebib-db-new-database))
        (duplicates '()))
    (let ((result (ebib--bib-find-bibtex-entries db nil)))
      (if (zerop (car result))
          (message "No entries found")
        (maphash
         (lambda (key value)
           (if (ebib-db-get-entry key ebib--cur-db t)
               (push key duplicates)))
         (ebib-db-val 'entries db))))
    (when duplicates
      (message "Found duplicate keys: %S" duplicates)
      (goto-char 0)
      (search-forward (car duplicates)))))

(defun ebib-capture-raw-bibtex (entry)
  (with-current-buffer (get-buffer-create "*Biblio entry*")
    (erase-buffer)
    (insert entry)
    (bibtex-mode)
    (ebib-capture-mode)
    (pop-to-buffer (current-buffer))
    (current-buffer)))


Run aspell from Windows Emacs using WSL

posted Sep 7, 2019, 10:45 AM by Juan Jose Garcia-Ripoll   [ updated Sep 7, 2019, 10:46 AM ]

If you have the Windows Subsystem for Linux, you can use its spell checker from Emacs. I have had to resort to this because hunspell.exe does no longer find my dictionaries and Emacs' code for hunspell is broken on Windows.

Step 1: Create a script called aspell.cmd with this content

@echo off
wsl aspell %1 %2 %3 %4 %5 %6 %7

Step 2: In your .emacs file, tell it to use this script for spell checking. Simply enter

(setq ispell-program-name "your/path/to/aspell.cmd")

If you now type M-$ (Esc followed by $ sign) you will enter the ispell mode. However, use of flyspell is more recommended.

Open mime attachments in Emacs

posted Jun 6, 2019, 12:37 AM by Juan Jose Garcia-Ripoll   [ updated Jun 6, 2019, 12:38 AM ]

As shown in a previous post, Emacs has some nice email readers. However, the default handling of most attachments (MIME types in traditional programming speak) is just to save the file. I have configured Emacs to rely on Windows for opening some types of attachments that I find often. As an example, I show below how to open PDFs and Word files, although it can be extended to other file types. Place this code in your Emacs' init.el file.

    (ctree-set-calist-strictly
     'mime-acting-condition
     '((mode . "play")
       (type . application)
       (subtype . pdf)
       (method . jjgr-default-mime-open)))
    (ctree-set-calist-strictly
     'mime-acting-condition
     '((mode . "play")
       (type . application)
       (subtype . vnd.openxmlformats-officedocument.wordprocessingml.document)
       (method . jjgr-default-mime-open)))

    (defun jjgr-default-mime-open (&optional a b)
      (let* ((entity (get-text-property (point) 'mime-view-entity))
             (name (mime-entity-safe-filename entity))
             (filename (concat (getenv "HOMEDRIVE")
                               (getenv "HOMEPATH")
                               "\\Downloads\\"
                               name)))
        (mime-write-entity-content entity filename)
        (print `(start-process "cmd.exe" "*Play*" "cmd.exe" ,filename))
        (start-process "cmd.exe" nil "cmd.exe" "/c" "start" "/b" filename)))

Handling email with Emacs' Wanderlust

posted May 3, 2019, 1:11 PM by Juan Jose Garcia-Ripoll   [ updated May 4, 2019, 3:17 AM ]

After some months using Thunderbird, I got a bit tired of some intrinsic problems with the speed of email synchronization, or the simple fact that saving an email to a folder would constantly fail. I have finally reverted back to handling email in Emacs, which is extremely productive, one you jump the hassle of configuring it.

My interface of choice is Wanderlust. There are some general guides for configuring it scattered through the web, plus a page on Emacs' wiki. In practice you will face some problems in the following fields:
  • Connection configuration: server, ports, type of connection, secrets, etc.
  • Handling and caching of emails.
  • Displaying and sending html emails.
  • Attachments
  • Contacts database.
I have not solved all of them, but my configuration is good enough that I think it is worth sharing. Here is the piece of code that I have in my Emacs' init file.

(use-package wl
  ;; The name of the package in MELPA is "wanderlust" but the
  ;; feature provided by that package is 'wl
  :ensure wanderlust
  :commands (wl)
  :bind
  (:map wl-summary-mode-map
        ("b a" . (lambda () (interactive) (djcb-wl-summary-refile "%Archives.2019")))
        ;;Swap a and A in summary mode, so citing original message is on a and no-cite on A.
        ("A" . wl-summary-reply)
        ("a" . wl-summary-reply-with-citation)
        ("r b" . jjgr-bbdb-mua-auto-update)
        )
  :hook
  ((wl-mail-send-pre . djcb-wl-draft-subject-check)
   (wl-mail-send-pre . djcb-wl-draft-attachment-check)
   (wl-mail-setup . wl-draft-config-exec))

  :config
  (progn
    (print "Wanderlust configured")
    (defun jjgr-bbdb-mua-auto-update ()
      (interactive)
      (wl-summary-enter-handler)
      (bbdb-mua-auto-update nil 'query)
      (mime-preview-quit))

    (defun djcb-wl-summary-refile (&optional folder)
      "refile the current message to FOLDER; if FOLDER is nil, use the default"
      (interactive)
      (wl-summary-refile (wl-summary-message-number) folder)
      (wl-summary-exec))

    ;; Store sent emails in the current folder
    (defun jjgr-determine-fcc-buffer ()
      (if (or (equal wl-message-buffer-cur-folder "%INBOX")
              (null wl-message-buffer-cur-folder))
          "%Sent"
        wl-message-buffer-cur-folder))
    (setq wl-fcc 'jjgr-determine-fcc-buffer)

    ;; Check messages for missing subject or abstract
    (defun djcb-wl-draft-subject-check ()
      "check whether the message has a subject before sending"
      (if (and (< (length (std11-field-body "Subject")) 1)
               (null (y-or-n-p "No subject! Send current draft?")))
          (error "Abort.")))

    (defun djcb-wl-draft-attachment-check ()
      "if attachment is mention but none included, warn the the user"
      (save-excursion
        (goto-char 0)
        (unless ;; don't we have an attachment?

            (re-search-forward "^Content-Disposition: attachment" nil t)
          (when ;; no attachment; did we mention an attachment?
              (or (re-search-forward "attach" nil t)
                  (re-search-forward "adjunt" nil t))
            (unless (y-or-n-p "Possibly missing an attachment. Send current draft?")
              (error "Abort."))))))

    (if (boundp 'mail-user-agent)
        (setq mail-user-agent 'wl-user-agent))
    (if (fboundp 'define-mail-user-agent)
        (define-mail-user-agent
          'wl-user-agent
          'wl-user-agent-compose
          'wl-draft-send
          'wl-draft-kill
          'mail-send-hook))

    (require 'bbdb)
    ) ; progn

  :init
  (setq wl-folders-file "~/OnlineFolder/Library/dot.folders"

        ;; SMTP server for mail posting.
        wl-smtp-posting-server "smtp.some.server.org"
        wl-smtp-posting-port 587
        wl-smtp-posting-user "user-name"
        wl-smtp-authenticate-type "plain"
        wl-smtp-connection-type 'starttls
        wl-from "some.email@some.server.org"
       
        ;; Do not cache passwords. The cache corrupts server
        ;; secrets.
        password-cache nil

        elmo-imap4-default-user "user-name"
        elmo-imap4-default-server "imap.some.server.org"
        elmo-imap4-default-port 993
        elmo-imap4-default-authenticate-type 'clear
        elmo-imap4-default-stream-type 'ssl
        elmo-passwd-storage-type 'auth-source

        ;; Location of archives
        elmo-archive-folder-path "~/OnlineFolder/Mail/"

        ;; Location of MH and Maildir folders
        elmo-localdir-folder-path "~/OnlineFolder/Mail/"
        elmo-maildir-folder-path "~/OnlineFolder/Mail/"

        wl-message-id-domain "some.email@some.server.org"
        wl-from "Some person <some.email@some.server.org>"
        mime-edit-default-signature "~/OnlineFolder/Library/dot.signature"
        wl-forward-subject-prefix "Fwd: "

        wl-default-folder "%INBOX" ;; my main inbox
        wl-biff-check-folder-list '("%INBOX") ;; check every 180 seconds
        wl-biff-check-interval 180

        wl-draft-folder "%Drafts"  ;; store drafts in 'postponed'
        wl-trash-folder "%Trash"   ;; put trash in 'trash'

        wl-stay-folder-window t
        wl-folder-window-width 25
        wl-folder-use-frame nil

        wl-message-ignored-field-list '("^.*")
        wl-message-visible-field-list '("^From:" "^To:" "^Cc:" "^Date:" "^Subject:")
        wl-message-sort-field-list wl-message-visible-field-list
        wl-summary-width 120 ;; No width
        wl-summary-default-sort-spec 'date
        wl-message-window-size '(1 . 2)

        ;; Always download emails without confirmation
        wl-prefetch-threshold nil
        wl-message-buffer-prefetch-threshold nil
        elmo-message-fetch-threshold nil

        ;; Rendering of messages using 'shr', Emacs' simple html
        ;; renderer, but without fancy coloring that distorts the
        ;; looks
        mime-view-text/html-previewer 'shr
        shr-use-colors nil

        wl-draft-config-alist
        '(((string-match "1" "1")
           (bottom . "\n--\n") (bottom-file . "~/OnlineFolder/Library/dot.signature"))
          )

        ;; don't ****ing split large messages
        mime-edit-split-message nil
        )
  ) ; use-package wanderlust

I keep these files in some online storage that I share between computers, which makes synchronizing databases easier. In ~/OnlineFolder/Library/ I create a dot.folders file with the folders that I am interested in from my IMAP server.

%INBOX "Inbox"
%Archives.2019 "Archivo"
%Drafts "Drafts"
%Sent "Sent"
%Trash "Trash"
%/

The final %/ is there to allow me to browse all other folders in the server, leaving them hidden by default.

The next step is to set up the contacts database. I import regularly contacts from Google, because they are kept up-to-date by the mobile phone. However, that requires some tweaking of standard libraries. Another goodie is the use of bbdb to auto-complete addresses when writing the header of an email, just by pressing <tab>. Finally, I have deactivated BBDB3's automatic gathering of contacts. The Wanderlust configuration invokes BBDB to do this when I press the key <r> followed by <b> in the email summary.

(use-package bbdb
  :ensure t
  :commands (bbdb-initialize)
  :hook
  ((mail-setup . bbdb-mail-aliases)
   (message-setup . bbdb-mail-aliases)
   (wl-mail-setup . jjgr-add-bbdb-tab-completion))

  :init
  (setq bbdb-file "~/OnlineFolder/Library/Emacs/bbdb"
        bbdb-mua-pop-up t
        bbdb-mua-pop-up-window-size t)

  :config
  (progn
    (bbdb-initialize 'wl)
    (bbdb-mua-auto-update-init 'wl)

    (defun my-bbdb-complete-mail ()
      "If on a header field, calls `bbdb-complete-mail' to complete the name."
      (interactive)
      (when (< (point)
               (save-excursion
                 (goto-char (point-min))
                 (search-forward (concat "\n" mail-header-separator "\n") nil 0)
                 (point)))
        (bbdb-complete-mail)))

    (defun jjgr-add-bbdb-tab-completion ()
      (define-key (current-local-map) (kbd "<tab>")
        'my-bbdb-complete-mail))
    ) ; progn
  ) ; use-package bbdb

(use-package bbdb-csv-import
  :ensure t
  :defer t
  :commands (bbdb-csv-import-file bbdb-csv-import-buffer)
  :after (bbdb)
  :config
  ;; Remove fields that Google Contacts creates, inserting them at the right
  ;; position.
  ;; 1. additional-name -> prepend it to lastname
  (defun jjgr-fix-bbdb-csv-import ()
    (interactive)
    (dolist (r (bbdb-records))
      (print r)
      (let* ((fields (bbdb-record-xfields r))
             (aname (assoc 'additional-name fields)))
        (when aname
          (print aname)
          (let ((new-lastname (concatenate 'string (cdr aname) " " (bbdb-record-lastname r))))
            ;;(setf (bbdb-record-lastname r) new-lastname)
            (bbdb-record-set-lastname r new-lastname)
            (bbdb-record-set-xfields r (assoc-delete-all 'additional-name fields))
            )))))

  )

Thunderbird shortcuts

posted Feb 8, 2019, 1:48 PM by Juan Jose Garcia-Ripoll

Mozilla Thunderbird
Thunderbird is a commonly used mail client that works on many platforms. It is a bit heavy and requires quite a lot of mouse use, as many graphical programs nowadays, but you can configure it to work better with a few keystrokes.

In order to follow these steps, please download first the add-on Dorando-keyconfig You can then configure Dorando with keybindings, adding the ones you find more suitable, and renaming the ones that are less standard. I list below the ones I use and change, just to remember that in my own machines.
  1. Navigate messages in a 'vim' fashion. You can reassign the "Previous > Message" or "Next > Message" to K and J, so that Thunderbird behaves just like vimium on Firefox.
  2. Zoom in and out conversations. I assign '+' and '-' to 'Conversation > Expand...' and 'Conversation > Contract'.
  3. Jump to specific folders. Right-click on the folder you want to use to find out its URI, the unique identifier or address that is used to find it. Then create a new keybinding with a code like the following one.
    var uri="imap://**the address of your folder**"
    for (i in gFolderTreeView._enumerateFolders) {
      var folder = gFolderTreeView._enumerateFolders[i]
      if (folder.URI==uri) {
        gFolderTreeView.selectFolder(folder)
      }
    }

  4. Archive email to specific folder. Find the URI of the destination folder and assign the following Javascript to the code you desire.
    MsgMoveMessage(MailUtils.getFolderForURI("imap://***@***/**URI to your folder**"))

  5. Save all attachments. Assign the following Javascript to Ctrl+Shift+S to save all the attachments in an email. It will open a dialogue to find a folder
    HandleAllAttachments("save")


Launch Jupyter notebooks automatically in Windows

posted Jan 26, 2019, 1:08 PM by Juan Jose Garcia-Ripoll   [ updated Jan 26, 2019, 1:09 PM ]

In latest editions of Anaconda, the installation wisely hides Python from the global path, but this also means that there are no good file associations for Python files or Jupyter notebooks.

One solution out there is nbopen. This utility launches one server and tries to reuse it with further notebooks. I have tried in the past, but it does not work if you want to work with notebooks in multiple directories. Somehow the tight permissions of Jupyter forbid it to access directories that are not inside the folder where it was initially started.

The only alternative that I have found is to build my own AutoHotKey script, which I can associate to *.ipynb files. This post documents how to do it. I assume that you have installed Miniconda for your own user and that the installation is located under %USERPROFILE%\Miniconda3
  1. Install AutoHotKey, a wonderful extension to map keys and build general scripts for Windows.
  2. Create and save the following script as start-jupyter-notebook.ahk
    #NoEnv
    filename=""
    EnvGet, home, USERPROFILE
    EnvGet, windir, windir
    If (%0% > 0) {
        loop, %1%
        file = %A_LoopFileFullPath%
        SplitPath, file, filename, directory
        filename := """" filename """"
    } Else {
        file := ""
        directory := home
    }
    miniconda := home "\Miniconda3"
    activate  := miniconda "\Scripts\activate.bat"
    command   := activate " " miniconda " && echo Working at directory && cd && jupyter notebook " filename
    Run, %windir%\system32\cmd.exe /c "%command%", %directory%, min

  3. Run the AutoHotKey compiler to convert it into an executable. I like to associate it with the Jupyter notebooks icon because then this icon is used by File Explorer. I call the executable "Open with Jupyter.exe" because it looks nice when I install it in the Start pane. The command line below one single line, sorry if it looks garbled:
    "%ProgramFiles%\AutoHotkey\Compiler\Ahk2Exe.exe" /in start-jupyter-notebook.ahk /out "Open with Jupyter.exe" /icon %USERPROFILE%\Miniconda3\Menu\jupyter.ico

  4. Open the properties for any Jupyter notebook (Right-click and select Properties) and click on "Change" to select the program that opens the notebooks. You may need to select "More programs" and use the explorer to find your compiled script.
The script will launch one server every time it is invoked. You can open futher files from within Jupyter using File -> Open in the Jupyter notebook menu.

When you are done, you can either kill the command line window with the Jupyter notebook or quit the server from the browser. For the latter, use File -> Open to open the Jupyter file browser and then click on Quit. You can then safely close all browser tabs.

A CSS stylesheet for exporting org-mode files

posted Apr 29, 2018, 1:49 PM by Juan Jose Garcia-Ripoll   [ updated Apr 30, 2018, 2:17 AM ]

I have been writing a big document using org-mode, exporting it to HTML and using the browser to generate a PDF from it. It looks pretty nice, similar to what I achieved with Word on an earlier version, and faster to write.

/*
 * CSS for org-mode document. Save with name style.css and include with
 * #+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style.css" />
 * It should be placed on the same directory as the HTML that is exported.
 *
 * Print options
 */

@page {
    margin: 2.54cm;
    size: A4;
    /* Page numbers, does not really work on most browsers. */
    @bottom-right {
        padding-right: 20px;
        content: "Page " counter(page);
    }
}

@media print {
    body {
        font-size: 11pt;
    }
    h2, h3, h4, h5 {
        page-break-after: avoid;
    }
    #table-of-contents {
        /* Page break after table of contents */
        page-break-after: always;
        margin-top: 2em;
        line-height: 1.5em;
        width: 80%;
        height: 40%;
        margin: auto auto;
    }
    #table-of-contents > h2 {
        text-align: center;
        margin-top: 4em;
    }
    .figure {
        /* Figure stays together with caption */
        page-break-inside: avoid;
    }
}

/*
 * For ordinary browsing
 */
@media screen {
    footer {
        display: none;
    }
    body {
        font-size: 12pt;
        margin-left: 2em;
        margin-right: 2em;
        max-width: 50em;
    }
    #table-of-contents {
        margin-top: 2em;
    }
}

/*
 * Common format
 */
body {
    font-family: Calibri;
    line-height: 1.2;
}

h1, h2, h3, h4, h5, h6 {
    margin-top: 0;
    margin-bottom: 6pt;
}

h2 {
    font-size: 1.3;
    text-transform: uppercase;
}

h1 {
    font-size: 1.6;
    font-variant: small-caps;
}

p {
    text-align: justify;
}

.figure > p {
    font-style: italic;
}

a:link, a:visited {
    text-decoration: none;
}

th.org-left {
    text-align: left;
}

/* Mark external links with an icon. */
a[href^="http://"]:after,
a[href^="https://"]:after {
    content: '\2021'; /*'\1F5D7';*/
    font-style: roman;
}

A sample file would be:
#+TITLE: Sample org file
#+STARTUP: inlineimages
#+LANGUAGE: es
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style.css" />

* First heading
* Second heading
** Subheading 1
** Subheading 2

#+caption: Some fancy figure
#+attr_html: :width 50%
[[./some-figure.png]]

The file can be simply exported using Ctrl-c Ctrl-e and then pressing 'h' followed by 'o' (HTML and Open in browser). Afterwards, printing can be done from the browser.

Edit: an example with output PDF is attached to this page.

Hope this is useful for you.

My .emacs configuration

posted Apr 9, 2018, 10:46 AM by Juan Jose Garcia-Ripoll   [ updated Apr 9, 2018, 1:06 PM ]

In case you like peeking into other people's configuration file or learn how to configure Emacs in Windows, here is my last set of settings.

Note: this relies on a personal fork of shackle to have LaTeX pop-up windows be automatically closed https://github.com/juanjosegarciaripoll/shackle when they are no longer used (AucTeX kills the buffer with errors but shackle does not close the window which showed the errors)
;;; -*- mode: emacs-lisp; -*-
;;; Different computers have different customizations due to
;;; monitor screen sizes, resolutions, etc.
;;;
(defun tic (&optional n)
  (print (list n (get-internal-run-time))))

;;;
;;; All our files will be located under a Dropbox common folder
;;; whose location may shift between computers. Also, fonts
;;; screen resolutions, etc, may vary.
;;;
(setq dropbox "~/Dropbox/Library/")
(setq dropbox-elpa "~/Dropbox/Library/Emacs/elpa-25")
(cond ((string= system-name "DEUTSCH")
       (setq juanjo:text-font-family "Noto Serif"
	     juanjo:text-font-height 110
	     juanjo:text-line-spacing 0.3
	     juanjo:margin 2))
      ((string= system-name "DESKTOP-HDKKLFG")
       (setq juanjo:text-font-height 140
	     juanjo:text-font-family "Vollkorn"
	     juanjo:text-line-spacing 0.0
	     juanjo:margin 1)
       (setq juanjo:text-font-family "Noto Serif"
	     juanjo:text-font-height 120
	     juanjo:text-line-spacing 0.3
	     juanjo:margin 2))
      (t
       (setq juanjo:text-font-family "DejaVu Serif"
	     juanjo:text-line-spacing 0.3
	     juanjo:text-font-height 110
	     juanjo:margin 1)))

(let ((custom-file (expand-file-name "dot.custom.el" dropbox)))
  (when (file-exists-p custom-file)
    (load custom-file)))

;;;
;;; Change to home directory. When using emacs.exe from Chocolatey,
;;; Emacs tends to start from the location of the shim executable.
;;;
(cd "~/")

(require 'package)
(setq package-user-dir dropbox-elpa)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/"))
(package-initialize)
;;(package-refresh-contents t)

;;;
;;; Lazy package management with postponed initialization, automatic
;;; installation of those packages.
;;;
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
(require 'use-package)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Persistence
;;;

;; Remember from session to session all the commands introduced
;; in the minibuffer, files opened, etc.
(setq savehist-file (expand-file-name "Emacs/dot.history" dropbox))
(savehist-mode 1)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Emacs quirks
;;;

;;; Do not use tabs when editing files
(setq-default indent-tabs-mode nil)
(setq tab-width 4)

;;; Make tabs indent first, then complete
(setq-default tab-always-indent 'complete)

;;; Remove yes-or-no questions, defaulting to single-keystroke
(fset 'yes-or-no-p 'y-or-n-p)

;;; Remove text from *scratch* buffer
(setq initial-scratch-message nil)

;;; Do not disable commands
(setq disabled-command-function nil)
;;(put 'downcase-region 'disabled nil)
;;(put 'scroll-left 'disabled nil)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Visual appearance:
;;
;; My preferred fonts for text and coding. I store them
;; as variables because they are used in buffer-local
;; customizations.
(setq code-face '(:family "Source Code Pro" :foundry "outline"
			  :slant normal :weight normal :height 98 :width normal)
      text-face `(:family ,juanjo:text-font-family :foundry "outline"
			  :slant normal :weight normal
			  :height ,juanjo:text-font-height :width normal)
      bold-text-face (plist-put (copy-sequence text-face) :weight 'bold))
(apply 'set-face-attribute 'default nil code-face)
;;
;; Darker background, softer coloring
(load "~/Dropbox/Library/Emacs/mycarthy-theme.el")
(load-theme 'mycarthy)
;; No blinking cursor
(blink-cursor-mode -1)
;; No fringe bars on both sides
(fringe-mode 0)
;; No continuation character on truncate-line mode
(set-display-table-slot standard-display-table 0 ?\ )

;;;
;;; Text editing with proportional fonts, good interline spacing
;;; and window margins.
;;;
(defun juanjo:text-mode-hooks ()
  ;; Some more spacing between lines
  (setq-local line-spacing juanjo:text-line-spacing)
  ;; Wrap around words
  (visual-line-mode +1)
  ;; Text modes should have proportional fonts
  (buffer-face-set text-face)
  (setq left-margin-width juanjo:margin
	right-margin-width juanjo:margin)
  )
(add-hook 'text-mode-hook 'juanjo:text-mode-hooks)

;;
;; Placement of windows
;;
(use-package shackle
  :load-path "~/Dropbox/Library/Emacs/local/shackle/"
  :commands shackle-mode
  :config
  (setq shackle-rules
	'(("\\*TeX.*\\*" :regexp t :autoclose t :align below :size 10)
	  ("\\*.*Help\\*" :regexp t :autoclose t :align below :size 10)))
  (setq shackle-default-rule '(:select t)))

(shackle-mode)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; My convenience bindings
;;;

;;; Delete whole buffer
(defun mps-clear-all ()
  (interactive)
  (delete-region (point-min) (line-end-position 0)))
(global-set-key [?\C-x ?\C-u] 'mps-clear-all)

;; (add-to-list 'load-path (concat dropbox "Emacs/fakecygpty/"))
;; (require 'fakecygpty)
;; (fakecygpty-activate)
;; (setq fakecygpty-ignored-program-regexps '("[cC][mM][dD]" "[cC][mM][dD][pP][rR][oO][xX][yY]"))
;; ;; We need to force using fakecygpty with tramp
;; (eval-after-load "tramp"
;;     '(progn
;;        (add-to-list 'tramp-methods
;;                     (mapcar
;;                      (lambda (x)
;;                        (cond
;;                         ((equal x "sshx") "cygssh")
;;                         ((eq (car x) 'tramp-login-program)
;; 			 (list 'tramp-login-program "fakecygpty ssh"))
;;                         (t x)))
;;                      (assoc "sshx" tramp-methods)))
;;        (setq tramp-default-method "cygssh")))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; LaTeX with AucTeX
;;;

;;
;; TeXcount setup for TeXcount version 2.3 and later
;;
(defun juanjo:texcount ()
  (interactive)
  (let*
    ((this-file (buffer-file-name))
     (enc-str (symbol-name buffer-file-coding-system))
     (enc-opt
      (cond
       ((string-match "utf-8" enc-str) "-utf8")
       ((string-match "latin" enc-str) "-latin1")
       ("-encoding=guess")
       ))
     (process-environment
      ;; Windows screwes this for me
      (append '("LANG=") process-environment))
     (word-count
      (with-output-to-string
	(with-current-buffer standard-output
	  (call-process "texcount" nil t nil "-0" enc-opt this-file))))
     )
    (message word-count)
    ))

(use-package latex
  :defer t
  :mode ("\\.tex\\'" . latex-mode)
  :bind
  (:map LaTeX-mode-map
	("C-c l" . TeX-error-overview)
        ("C-c w" . juanjo:textcount))

  :config
  ;; Minor changes to the LaTeX mode
  (add-hook 'LaTeX-mode-hook 'juanjo:latex-hooks)
  (add-hook 'LaTeX-mode-hook 'turn-on-reftex)
  (add-hook 'LaTeX-mode-hook 'juanjo:latex-hooks)
  (add-hook 'LaTeX-mode-hook 'flyspell-mode)

  (setq TeX-PDF-mode t
	TeX-save-query nil
	TeX-source-correlate-mode t
	TeX-source-correlate-method 'synctex
	;; Tell SumatraPDF to open the current PDF file, indicating
	;; which is the source file and line number (used by synctex)
	;; and provide a valid command line to edit at the desired
	;; location of a source file that is linked in the PDF.
	TeX-view-program-list
	'(("Sumatra PDF"
	   ("\"SumatraPDF.exe\" -reuse-instance "
	    "-inverse-search \"emacsclientw.exe +%%l \\\"%%f\\\"\" "
	    (mode-io-correlate " -forward-search %a %n ")
	    " %o")))
	TeX-view-program-selection
	'(((output-dvi style-pstricks) "dvips and start")
	  (output-dvi "Yap")
	  (output-pdf "Sumatra PDF")
	  (output-html "start"))
	;; Open TeX error list after compilation
	;; and show all warnings
	TeX-error-overview-open-after-TeX-run t
	TeX-debug-warnings t)
  
  ;; Replace viewer with SumatraPDF
  (assq-delete-all 'output-pdf TeX-view-program-selection)
  (add-to-list 'TeX-view-program-selection
	       '(output-pdf "Sumatra PDF"))

  ) ; use-package latex

;;
;; Hook into font-latex to reduce the size of some section,
;; chapter, subscript and superscript fonts
;;
(use-package font-latex
  :defer t
  :config
  (set-face-attribute 'font-latex-subscript-face nil :height 0.6)
  (set-face-attribute 'font-latex-superscript-face nil :height 0.6)
  ;; I do not like large fonts for the sections, chapters, etc
  (let ((height (round (* 1.1 juanjo:text-font-height))))
    (dolist (face '(font-latex-sectioning-0-face
		    font-latex-sectioning-1-face
		    font-latex-sectioning-2-face
		    font-latex-sectioning-3-face
		    font-latex-sectioning-4-face
		    font-latex-sectioning-5-face))
      (apply 'set-face-attribute face nil
	     (plist-put (copy-sequence bold-text-face)
			:height height))))
  )

(use-package reftex
  :defer t
  :commands turn-on-reftex
  :init
  (setq reftex-plug-into-AUCTeX t
	;; RefTeX list of sections, labels and figures shows as
	;; vertical bar to the left of the window.
	reftex-toc-split-windows-horizontally t
	;; RefTeX table of contents does not indicate which
	;; sections are in which files.
	reftex-toc-include-file-boundaries nil))

(use-package latex-extra
  :defer t
  :ensure t
  :commands latex-extra-mode
  :bind
  (:map LaTeX-mode-map 
	("M-" . latex/next-section-same-level)
	("M-" . latex/previous-section-same-level))
  :init
  (add-hook 'LaTeX-mode-hook (lambda () (latex-extra-mode) (auto-fill-mode -1)))
  ;; Do not override AucTex's font commands
  (setq latex/override-font-map nil))

(defun delatexify ()
  (save-excursion
    (goto-char 0)
    (replace-string "{\'\i}" "í")
    (replace-string "\\'o" "ó")
    (replace-string "\\'a" "á")
    (replace-string "\\'e" "é")
    (replace-string "\\'u" "ú")
    (replace-string "\\\"a" "ä")
    (replace-string "\\\"o" "ö")))

(defun juanjo:html-mode-hooks()
  ;; Wrap around words
  (visual-line-mode +1)
  ;; Text modes should have proportional fonts
  (buffer-face-set code-face))

(add-hook 'html-mode-hook 'juanjo:html-mode-hooks)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Dictionaries, languages and encodings
;;
;;
;; Use hunspell.exe for automatic spell checking. Available
;; from Chocolately as choco install hunspell
;;
(use-package flyspell
  :defer t
  :bind
  (("" . ispell-word)
   ("C-S-" . flyspell-mode)
   ("C-M-" . flyspell-buffer)
   ("C-" . flyspell-check-previous-highlighted-word)
   ("M-" . flyspell-check-next-highlighted-word))
  :init
  (setenv "DICTPATH" "c:\\ProgramData\\chocolatey\\lib\\hunspell.portable\\tools\\share\\hunspell\\")
  (setenv "DICTIONARY" "c:\\ProgramData\\chocolatey\\lib\\hunspell.portable\\tools\\bin\\..\\share\\hunspell\\en_US")
  (setq ispell-program-name "c:\\ProgramData\\chocolatey\\bin\\hunspell.exe"
	;; Save dictionary in common location
	ispell-extra-args `("-p" ,(expand-file-name "hunspell" dropbox))
	;; Save dictionary without asking
	ispell-silently-savep t
	;; Do not issue warnings for all wrong words
	flyspell-issue-message-flag nil)

  (defun flyspell-check-next-highlighted-word ()
    "Custom function to spell check next highlighted word"
    (interactive)
    (flyspell-goto-next-error)
    (ispell-word)
    )

  :config
  (ispell-change-dictionary "en_US" t)

  ) ; use-package flyspell

;;; Encoding for everything
(prefer-coding-system 'utf-8-unix)

;;;
;;; Convenience keybindings for Greek letters
;;;
(progn
  (global-set-key (kbd "C-x C-g a") "α")
  (global-set-key (kbd "C-x C-g b") "β")
  (global-set-key (kbd "C-x C-g g") "γ")
  (global-set-key (kbd "C-x C-g d") "δ")
  (global-set-key (kbd "C-x C-g ep") "ε")
  (global-set-key (kbd "C-x C-g z") "ζ")
  (global-set-key (kbd "C-x C-g et") "η")
  (global-set-key (kbd "C-x C-g Th") "θ")
  (global-set-key (kbd "C-x C-g i") "ι")
  (global-set-key (kbd "C-x C-g í") "ί")
  (global-set-key (kbd "C-x C-g k") "κ")
  (global-set-key (kbd "C-x C-g l") "λ")
  (global-set-key (kbd "C-x C-g m") "μ")
  (global-set-key (kbd "C-x C-g n") "ν")
  (global-set-key (kbd "C-x C-g xi") "ξ")
  (global-set-key (kbd "C-x C-g o") "ο")
  (global-set-key (kbd "C-x C-g ó") "ό")
  (global-set-key (kbd "C-x C-g pi") "π")
  (global-set-key (kbd "C-x C-g r") "ρ")
  (global-set-key (kbd "C-x C-g fs") "ς")
  (global-set-key (kbd "C-x C-g s") "σ")
  (global-set-key (kbd "C-x C-g t") "τ")
  (global-set-key (kbd "C-x C-g y") "υ")
  (global-set-key (kbd "C-x C-g ý") "ύ")
  (global-set-key (kbd "C-x C-g ph") "φ")
  (global-set-key (kbd "C-x C-g chi") "φ")
  (global-set-key (kbd "C-x C-g ps") "ψ")
  (global-set-key (kbd "C-x C-g w") "ω"))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Windows and unix-like shells
;;
(defun cygwin-shell ()
  "Run cygwin bash in shell mode."
  (interactive)
  (let ((explicit-shell-file-name "bash")
	(explicit-bash-args '("-i")))
    (call-interactively 'shell)))

(defun wunix-shell ()
  "Run Windows' bash in shell mode."
  (interactive)
  (let ((explicit-shell-file-name "cmd.exe")
	(explicit-cmd.exe-args '("/C" "c:\\Windows\\WinSxS\\amd64_microsoft-windows-lxss-bash_31bf3856ad364e35_10.0.16299.15_none_62878a822db68b25\\bash.exe" "-i")))
    (call-interactively 'shell)))

(defun visual-studio-2017-x84-shell ()
  "Run Windows shell with Visual Studio environment."
  (interactive)
  (let ((explicit-shell-file-name "cmd.exe")
	(explicit-cmd.exe-args '("/k" "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat")))
    (shell)))

(setq tramp-default-method "ssh")

(defun browser-sync ()
  "Starts a browser-sync script if it exists in this directory"
  (interactive)
  (start-process "browser-sync" "*browser-sync*" (expand-file-name "debug.cmd")))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Email, web and other services
;;

(use-package wunderlist
  :defer t
  :init
  (setq wl-folders-file "~/Dropbox/Library/dot.folders"

        ;; SMTP server for mail posting. Default: nil
        wl-smtp-posting-server "smtpin.xxxxxx.es"
        wl-smtp-posting-port 465
        wl-smtp-posting-user "xxxxx"
        wl-smtp-authenticate-type "plain"
        wl-smtp-connection-type 'ssl
        wl-from "xxxx@xxx.es"
        smtp-fqdn "xxx.xxxx.es"

        elmo-imap4-default-user "xxxxx"
        elmo-imap4-default-server "xxxx.xxx.es"
        elmo-imap4-default-port 993
        elmo-imap4-default-authenticate-type 'clear
        elmo-imap4-default-stream-type 'ssl

        ;; Location of archives
        elmo-archive-folder-path "~/Dropbox/Mail/"

        ;; Location of MH and Maildir folders
        elmo-localdir-folder-path "~/Dropbox/Mail/"
        elmo-maildir-folder-path "~/Dropbox/Mail/"

        wl-message-id-domain "xxxx@xxxx.es"
        wl-from "Juan Jose Garcia-Ripoll "

        wl-stay-folder-window t
        wl-folder-window-width 25
        wl-folder-use-frame nil

        wl-message-ignored-field-list '("^.*")
        wl-message-visible-field-list '("^From:" "^To:" "^Cc:" "^Date:" "^Subject:")
        wl-message-sort-field-list wl-message-visible-field-list
        wl-summary-default-sort-spec 'date
        wl-message-window-size '(1 . 3)
        )

  :config
  (if (boundp 'mail-user-agent)
      (setq mail-user-agent 'wl-user-agent))
  (if (fboundp 'define-mail-user-agent)
      (define-mail-user-agent
        'wl-user-agent
        'wl-user-agent-compose
        'wl-draft-send
        'wl-draft-kill
        'mail-send-hook))

  ) ; use-package wunderlist

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Auto completion with IDO
;;;

(use-package ido
  :ensure t
  :init  (setq ido-enable-flex-matching t
               ido-ignore-extensions t
               ido-use-virtual-buffers t
               ido-everywhere t)
  :config
  (ido-mode 1)
  (ido-everywhere 1)
  (add-to-list 'completion-ignored-extensions ".pyc"))

(use-package flx-ido
   :ensure t
   :init (setq ido-enable-flex-matching t
               ido-use-faces nil)
   :config (flx-ido-mode 1))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Version control
;;;

(use-package magit
  :defer t
  :ensure t
  )

Custom skin for Jupyter notebooks

posted Apr 4, 2018, 7:28 AM by Juan Jose Garcia-Ripoll   [ updated Apr 4, 2018, 7:29 AM ]

This is going to be a brief entry. Essentially: I have reading problems, which together with the strain induced by bad quality monitors (wish all were as good as the tiny screen of my Surface Pro), force me to look for ways to improve the quality of text I have to read on screen.

One problem for me as of late are Jupyter notebooks. Their default look in Windows involves narrow lines, bright background and sometimes too broad coding fonts, plus wasted space around prompts, line numbers, etc.

Fortunately, it seems you can customize the look of notebooks by touching one file. Follow these steps:
  1. Guess the location of Jupyter's configuration. For that open a command line and type jupyter --config-dir That should output a directory such as E:\home\JuanJose\.jupyter
  2. Create a file in that directory. More precisely, for my example, it will be E:\home\JuanJose\.jupyter\custom\custom.css
  3. Enter some CSS styles in that file. I copy my own below.
The effects should be visible once you reload the notebook, even if a session is open.

/*
 * Comment out the following line if you have installed
 * the following fonts:
 *   - Source Code Pro,
 *     https://fonts.google.com/?selection.family=Source+Code+Pro
 *   - Vollkorn,
 *     https://fonts.google.com/specimen/Vollkorn
 */
@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro');
@import url('https://fonts.googleapis.com/css?family=Vollkorn');
#notebook, div#notebook {
    font-family: 'Vollkorn', 'DejaVu Serif', 'Palatino Linotype', serif;
    font-size: 16px;
    line-height: 1.5;
}
h1,h2,h3,h4,h5,h6 {
    font-family: 'Palatino Linotype';
}
body {
    font-family: 'Verdana', 'Arial', sans-serif;
}
body, #notebook-container, body > #header, .btn-default {
    background-color: rgb(252,251,251);
}
li, .MathJax_Display {
    margin-bottom: 6px;
}
code, kbd, pre, samp, .CodeMirror {
    font-family: Source Code Pro;
    font-size: 12px;
}
pre {
    font-size: 11px;
}
.prompt {
    font-family: Arial;
    font-size: 11px;
    min-width: 6em;
}
.input-prompt {
    color: rgb(108, 119, 188);
}
.output-prompt {
    color: rgb(205, 129, 106);
}

Some screenshots of my new skin, with Windows Edge and Google Chrome. Windows Edge tends to increase the perceived weight of characters, which is not too bad in my case.



1-10 of 32