Skip to content

AfsmNGhr/dockemacs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

52b29d0 · Jan 24, 2025
Mar 13, 2017
May 31, 2023
Oct 9, 2021
Jul 7, 2018
Sep 13, 2017
Oct 9, 2021
Feb 4, 2024
Jan 1, 2018
Aug 31, 2017
Feb 4, 2024
Nov 23, 2017
Nov 23, 2017
May 31, 2023
Jan 24, 2025

Repository files navigation

Dockemacs - the minimal emacs noX

https://coveralls.io/repos/github/AfsmNGhr/dockemacs/badge.svg?branch=master https://img.shields.io/docker/v/afsmnghr/dockemacs.svg?style=flat https://img.shields.io/docker/image-size/afsmnghr/dockemacs.svg?style=flat https://img.shields.io/docker/pulls/afsmnghr/dockemacs.svg?style=flat https://img.shields.io/docker/stars/afsmnghr/dockemacs.svg?style=flat

https://i.imgur.com/V6vlv7Q.gif

Table of Contents

Installation

Docker

Install Docker Engine

Create emacs data volume.

docker volume create emacs_data

Executable

Add alias:

# ~/.bash_aliases or etc ...

alias dockemacs='
  docker run -it --rm --net=host \
       --cpuset-cpus 0-1 \
       --env-file $HOME/.dockemacs \
       --entrypoint initialize "$@" \
       -v $HOME:/mnt/workspace \
       -v emacs_data:/home/emacser/.emacs.d \
       -v /etc/localtime:/etc/localtime:ro \
       afsmnghr/dockemacs:1.15.0 startup
'

Environment variables

Prepare $HOME/.dockemacs, check your env.

# default by

## transparent permissions
echo "UID=$(id -u)" >> $HOME/.dockemacs # 1000
echo "GID=$(id -g)" >> $HOME/.dockemacs # 100

## user & group name in container
echo "UNAME=emacser" >> $HOME/.dockemacs
echo "GNAME=emacs" >> $HOME/.dockemacs

## rewrite home path for new user
echo "HOME=/home/emacser" >> $HOME/.dockemacs

## mount path from host
echo "WORKSPACE=/mnt/workspace" >> $HOME/.dockemacs

# required

## set terminal env
echo "TERM=xterm-256color" >> $HOME/.dockemacs

## only relative path from workspace path
echo "ORG_FILES=Documents/org/" >> $HOME/.dockemacs

## remote management through ssh
echo "HOST_USER=afsmnghr" >> $HOME/.dockemacs
echo "HOST_IP=127.1" >> $HOME/.dockemacs # only work with --net=host
echo "HOST_PORT=22" >> $HOME/.dockemacs

## required for GUI application
echo "DISPLAY=:0.0" >> $HOME/.dockemacs
## setup browser for emacs
echo "WEB_BROWSER=chromium" >> $HOME/.dockemacs

## our repository dotemacs (first clone)
echo "REPOSITORY=https://github.com/AfsmNGhr/dockemacs.git" >> $HOME/.dockemacs
## our active branch
echo "BRANCH=master" >> $HOME/.dockemacs

# optional

## force update our branch
echo "HEAD_FORCE=true" >> $HOME/.dockemacs # git reset --hard

Escape in the box

Setup ssh server and restart.

# /etc/ssh/sshd_config

ListenAddress 127.1

Setup ssh client. Create sockets path.

mkdir ~/.ssh/sockets

Speedup local connection.

# ~/.ssh/config

Host *
     ControlMaster auto
     ControlPath ~/.ssh/sockets/%r@%h:%p
     ControlPersist 4h
     PreferredAuthentications publickey

Host 127.1
     Hostname 127.1
     User "$HOST_USER"
     Port "$HOST_PORT"
     Compression no
     Ciphers [email protected]
     ForwardX11 no

Check permissions of config file.

sudo chmod 600 ~/.ssh/config

Add our ssh pub key to authorized_keys.

ssh-copy-id "$HOST_USER@$HOST_IP" -p "$HOST_PORT"

For SSH_CONNECTION set TERM. Fixed tramp issues.

# ~/.bashrc

if [ "$SSH_CONNECTION" ]; then
    TERM='dumb'
fi

case "$TERM" in
    *)
        PS1='> '
        ;;
esac

Prepare gpg settings.

# ~/.gnupg/gpg.conf

use-agent
pinentry-mode loopback

Starting

Run and wait until the boot.

$ dockemacs

Initialization

Tangling with emacs script. See emacs script pitfalls.

#!/usr/bin/env sh
":"; exec emacs --quick --script "$0" "$@" # -*-emacs-lisp-*-

(require 'org)
(setq gc-cons-threshold most-positive-fixnum
      gc-cons-percentage 0.6)
(find-file (concat user-emacs-directory "init.org"))
(org-babel-tangle)
(load-file (concat user-emacs-directory "init.el"))
(byte-compile-file (concat user-emacs-directory "init.el"))
(setq gc-cons-threshold 5000000
      gc-cons-percentage 0.1)

Perfomance

Garbage collector

(defmacro k-time (&rest body)
  "Measure and return the time it takes evaluating BODY."
  `(let ((time (current-time)))
     ,@body
     (float-time (time-since time))))

;; When idle for 15sec run the GC no matter what.
(defvar k-gc-timer
  (run-with-idle-timer 15 t
                       (lambda ()
                         (message "Garbage collector has run for %.06f sec"
                                  (k-time (garbage-collect))))))

Package Management

Don’t auto-initialize!

(setq package-enable-at-startup nil
      package--init-file-ensured t)

The use-package declarative and performance-oriented.

(require 'package)
(package-initialize)

(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
                         ("org" . "http://orgmode.org/elpa/")
                         ("melpa" . "https://melpa.org/packages/")
                         ("melpa-stable" . "https://stable.melpa.org/packages/")))

(unless (version< emacs-version "25.1")
  (setq package-archive-priorities '(("melpa-stable" . 30)
                                     ("gnu" . 10)
                                     ("melpa" . 20))))

(unless package-archive-contents
  (package-refresh-contents))

(let ((afsmnghr/packages '(use-package)))
  (dolist (p afsmnghr/packages)
    (unless (package-installed-p p)
      (package-install p))))

(eval-when-compile
  (require 'use-package))

Diminished modes.

(use-package delight :ensure t)

Key-bindings.

(use-package bind-key :ensure t)

Interface

Don’t store customizations.

(use-package cus-edit :defer t
  :commands (customize-set-variable)
  :custom (custom-file null-device))

Short, answering yes or no.

(fset 'yes-or-no-p 'y-or-n-p)

Clear UI.

(menu-bar-mode -1)
(if tool-bar-mode
    (tool-bar-mode -1))
(column-number-mode -1)
(blink-cursor-mode -1)
(line-number-mode -1)
(size-indication-mode -1)
(setq ring-bell-function 'ignore)

Time in the modeline.

(setq display-time-interval 1
      display-time-format "%H:%M"
      display-time-default-load-average nil)

(display-time-mode)

Dialogs stay in emacs.

(setq use-dialog-box nil
      use-file-dialog nil
      epg-pinentry-mode 'loopback)

Unsorted settings.

(setq show-paren-style 'mixed
      word-wrap t
      search-highlight t
      query-replace-highlight t
      select-enable-clipboard t
      echo-keystrokes 0.1
      enable-local-eval t)

Themes

Load my themes. Enable theme on the frame type.

(defun afsmnghr/load-theme ()
  "Load a theme"
  (add-to-list 'custom-theme-load-path "~/.emacs.d/themes")

  (if (display-graphic-p)
      (load-theme 'spolsky t)
    (load-theme 'spolsky-term t)))

(defun afsmnghr/enable-theme (frame)
  "Enable theme the current frame depending on the frame type"
  (with-selected-frame frame
    (if (window-system)
        (progn
          (unless (custom-theme-enabled-p 'spolsky)
            (if (custom-theme-enabled-p 'spolsky-term)
                (disable-theme 'spolsky-term))
            (enable-theme 'spolsky)))
      (progn
        (unless (custom-theme-enabled-p 'spolsky-term)
          (if (custom-theme-enabled-p 'spolsky)
              (disable-theme 'spolsky))
          (enable-theme 'spolsky-term))))))

(add-hook 'after-init-hook 'afsmnghr/load-theme)

;; don't change theme inside docker container
(unless (file-exists-p "/.dockerenv")
  (add-hook 'after-make-frame-functions 'afsmnghr/enable-theme))
Spolsky

images/spolsky-theme.png

Spolsky Term

images/spolsky-term-theme.png

Built-in

Enable built-in modes.

(global-visual-line-mode t)
(global-font-lock-mode t)
(global-auto-revert-mode t)
(delete-selection-mode t)

Dired listing settings.

(setq dired-listing-switches "-lhvA")

Encoding

Set utf-8 everywhere.

(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(setq buffer-file-coding-system 'utf-8
      file-name-coding-system 'utf-8)

Indentation

Prefer space indentation.

(setq-default tab-width 2
              tab-always-indent 'complete
              indent-tabs-mode nil)

Autopair

(use-package elec-pair
  :commands electric-pair-mode
  :config (electric-pair-mode 1))

Whitespace

(use-package whitespace
  :hook (prog-mode . whitespace-mode)
  :custom
  ((whitespace-line-column 120)
   (whitespace-style '(face lines-tail))))

Keybindings

Add comment fn.

(defun comment-or-uncomment-region-or-line ()
  "Un / Comments the region or the current line if there's no active region."
  (interactive)
  (let (beg end)
    (if (region-active-p)
        (setq beg (region-beginning) end (region-end))
      (setq beg (line-beginning-position) end (line-end-position)))
    (comment-or-uncomment-region beg end)
    (forward-line)))

My keybindings almost defaulted.

(global-set-key (kbd "C-x w") 'kill-buffer-and-window)
(global-set-key (kbd "C-z") 'undo)

(global-set-key (kbd "C-x o") 'ace-window)

(global-set-key (kbd "C-w") 'clipboard-kill-region)
(global-set-key (kbd "M-w") 'clipboard-kill-ring-save)

(global-set-key (kbd "C-y") 'clipboard-yank)
(global-set-key (kbd "M-q") 'query-replace-regexp)

(global-set-key [remap comment-dwim] 'comment-or-uncomment-region-or-line)

Reverse input.

(use-package reverse-im :ensure t :defer 1
  :commands reverse-im-activate
  :config (reverse-im-activate "russian-computer"))

History

(setq history-length t
      history-delete-duplicates t
      savehist-save-minibuffer-history 1
      savehist-autosave-interval 60
      savehist-additional-variables '(search-ring regexp-search-ring comint-input-ring))

(savehist-mode 1)

Backups

(setq backup-directory-alist '(("." . "~/.emacs.d/backups"))
      auto-save-file-name-transforms '((".*" "~/.emacs.d/auto-save-list/" t))
      delete-old-versions t
      version-control t
      vc-make-backup-files t
      backup-by-copying t
      kept-new-versions 2
      kept-old-versions 2)

Recent files

(use-package recentf :defer t
  :after ido
  :init (recentf-mode 1)
  :commands recentf-mode
  :custom ((recentf-max-saved-items 30)
           (recentf-keep '(file-remote-p file-readable-p)))
  :config (run-with-idle-timer 10 t 'recentf-save-list))

Bookmarks

(use-package bookmark :defer t
  :after ido
  :custom (bookmark-save-flag t)
  :commands (bookmark-jump bookmark-all-names)
  :preface
  (defun jump-to-bookmark ()
    (interactive)
    (bookmark-jump
     (ido-completing-read "Jump to bookmark: "
                          (bookmark-all-names))))
  :bind
  (:map global-map ("C-x r b" . jump-to-bookmark)))

Hooks

(defadvice save-buffers-kill-emacs (around no-query-kill-emacs activate)
  "Prevent annoying \"Active processes exist\" query when you quit Emacs."
  (cl-letf (((symbol-function #'process-list) (lambda ())))
    ad-do-it))

(defun tangle-init ()
  "If the current buffer is 'init.org' the code-blocks are
tangled, and the tangled file is compiled."
  (when (equal (buffer-file-name)
               (expand-file-name (concat user-emacs-directory "init.org")))
    ;; Avoid running hooks when tangling.
    (let ((prog-mode-hook nil))
      (org-babel-tangle)
      (byte-compile-file (concat user-emacs-directory "init.el")))))

(defun afsmnghr/minibuffer-setup-hook ()
  (setq gc-cons-threshold most-positive-fixnum
        gc-cons-percentage 0.6))

(defun afsmnghr/minibuffer-exit-hook ()
  (setq gc-cons-threshold 5000000
        gc-cons-percentage 0.1))

(add-hook 'minibuffer-setup-hook #'afsmnghr/minibuffer-setup-hook)
(add-hook 'minibuffer-exit-hook #'afsmnghr/minibuffer-exit-hook)
(add-hook 'after-save-hook #'tangle-init)
(add-hook 'before-save-hook #'delete-trailing-whitespace)

Window management

Named buffers.

(use-package ace-window :ensure t :defer t
  :custom ((aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
           (aw-background nil)))

Completion

IDO

Enable ido (or “Interactively DO things”) everywhere.

(use-package ido-hacks :ensure t :defer 1)

(use-package flx-ido :ensure t
  :after ido
  :commands (flx-ido-mode ido-everywhere flx-ido-mode)
  :init
  (ido-mode 1)
  (ido-everywhere 1)
  (flx-ido-mode 1)
  :custom
  ((flx-ido-threshold 1000)
   (ido-enable-flex-matching t)
   (ido-use-faces t)
   (ido-virtual-buffers t)
   (ido-auto-merge-work-directories-length -1)))

(use-package ido-completing-read+ :ensure t :pin melpa-stable
  :after ido
  :commands ido-ubiquitous-mode
  :init (ido-ubiquitous-mode 1))

Company

Use modern completion framework.

(use-package company :ensure t :defer 30
  :init (global-company-mode t)
  :commands global-company-mode
  :custom ((company-backends '((company-files company-keywords company-capf company-dabbrev-code)))
           (company-idle-delay 0.5)
           (company-tooltip-flip-when-above t)
           (company-dabbrev-downcase nil)))

(use-package company-flx :ensure t :defer t
  :after company
  :commands company-flx-mode
  :init (with-eval-after-load 'company
          (company-flx-mode +1)))

VCS

Magit

It’s Magit! A Git porcelain inside Emacs.

(use-package magit :ensure t :defer 1
  :unless (version< emacs-version "24.4")
  :custom
  ((magit-completing-read-function 'magit-ido-completing-read)
   (magit-branch-arguments nil)
   (magit-status-margin '(t "%Y-%m-%d %H:%M " magit-log-margin-width t 18))
   (magit-default-tracking-name-function 'magit-default-tracking-name-branch-only)
   (magit-set-upstream-on-push t)
   (magit-push-always-verify nil)
   (magit-restore-window-configuration t)
   (vc-handled-backends nil)))

Git time machine

Travel back and forward in git history with git time machine.

(use-package git-timemachine :ensure t :defer t
  :unless (version< emacs-version "24.4"))

Smerge-mode

Merging conflicts.

(use-package smerge-mode :defer t)

Project management

Setup projectile.

(use-package projectile :ensure t :defer 1
  :delight '(:eval
             (propertize (concat " " (projectile-project-name))
                         'face '(:foreground "#FD971F")))
  :commands projectile-mode
  :init
  (projectile-mode)
  (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
  :custom
  ((projectile-enable-caching t)
   (projectile-use-git-grep t)
   (projectile-indexing-method 'native)
   (projectile-sort-order 'recentf)
   (projectile-switch-project-action 'projectile-dired)
   (projectile-file-exists-remote-cache-expire (* 10 60))
   (projectile-file-exists-local-cache-expire (* 5 60))
   (projectile-require-project-root nil)
   (projectile-idle-timer-seconds 60)
   (projectile-completion-system 'ido)))

Search

Ag

Use it for projectile and dumb-jump.

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

Tags

Grepping tags.

(use-package dumb-jump :ensure t :defer t
  :bind (("M-g o" . dumb-jump-go-other-window)
         ("M-g j" . dumb-jump-go)
         ("M-g i" . dumb-jump-go-prompt)
         ("M-g x" . dumb-jump-go-prefer-external)
         ("M-g z" . dumb-jump-go-prefer-external-other-window))
  :custom ((dumb-jump-selector 'ido)
           (dumb-jump-prefer-searcher 'git-grep)
           (dumb-jump-force-searcher 'ag)))

DevOps

Simple management docker containers.

(use-package docker :ensure t :defer t
  :unless (version< emacs-version "24.4"))

Major mode for Dockerfile.

(use-package dockerfile-mode :ensure t :defer t
  :mode (("Dockerfile\\'" . dockerfile-mode)))

Languages

Elixir

(use-package elixir-mode :ensure t :defer t)

Python

(use-package python :defer t)

Javascript

(use-package typescript-mode :ensure t :defer t
  :custom (typescript-indent-level 2))

(use-package json :ensure t :defer t
  :custom (js-indent-level 2))

(use-package js2-mode :ensure t :defer t
  :mode (("\\.js\\'" . js2-mode)
         ("\\.json\\'" . javascript-mode))
  :commands js2-mode
  :custom
  ((js2-basic-offset 2)
   (js2-indent-switch-body t)
   (js2-auto-indent-p t)
   (js2-highlight-level 3)
   (js2-indent-on-enter-key t)))

Templates

(use-package markdown-mode :ensure t :defer t)
(use-package css-mode :ensure t :defer t)
(use-package sass-mode :ensure t :defer t
  :mode (("\\.scss" . sass-mode)))
(use-package yaml-mode :ensure t :defer t)
(use-package web-mode :ensure t :defer t
  :commands web-mode
  :mode (("\\.html?\\'" . web-mode)
         ("\\.erb\\'" . web-mode)
         ("\\.vue" . web-mode)
         ("\\.jsx" . web-mode)
         ("\\.tsx" . web-mode))
  :custom ((web-mode-markup-indent-offset 2)
           (web-mode-enable-auto-pairing t)
           (web-mode-enable-current-element-highlight t)
           (web-mode-enable-block-face t)
           (web-mode-enable-part-face t)))

Org

Save org buffers.

(defun afsmnghr/before-kill-emacs ()
  (if (fboundp 'org-save-all-org-buffers)
      (org-save-all-org-buffers)))

(add-hook 'kill-emacs-hook #'afsmnghr/before-kill-emacs)

Main org.

(use-package org :defer 3
  :config
  (custom-set-variables
   '(org-babel-load-languages
     (quote ((emacs-lisp . t) (python . t) (shell . t) (js . t) (sql . t))))
   '(org-confirm-babel-evaluate nil))
  :custom
  ((org-log-done t)
   (org-directory (getenv "ORG_PATH"))
   (org-startup-indented t)
   (org-indent-mode-turns-on-hiding-stars nil)
   (org-todo-keywords
    '((sequence "TODO(t!)" "NEXT(n@/!)" "INPROGRESS(i!)" "HOLD(h@/!)"
                "DONE(d!)" "CANCELLED(c@/!)"))))
  :bind
  (:map global-map ("C-c a" . org-agenda)))

Org faces. Prepare colors for to do list.

(use-package org-faces
  :after org
  :custom
  ((org-todo-keyword-faces
    '(("INPROGRESS" :foreground "DodgerBlue2" :weight bold)
      ("HOLD" :foreground "firebrick2" :weight bold)
      ("NEXT" :foreground "OrangeRed2" :weight bold)))
   (org-priority-faces '((?A . (:foreground "firebrick2" :weight 'bold))
                         (?B . (:foreground "OrangeRed2"))
                         (?C . (:foreground "DodgerBlue2"))))))

Org blocks of sources.

(use-package org-src
  :after org
  :custom
  ((org-src-fontify-natively t)
   (org-edit-src-content-indentation 2)
   (org-src-tab-acts-natively t)
   (org-src-preserve-indentation t)
   (org-src-window-setup 'current-window)
   (org-src-ask-before-returning-to-edit-buffer nil)))

Org agenda settings.

(use-package org-agenda
  :after org
  :custom
  ((org-agenda-files (list org-directory (concat org-directory "orgzly")))
   (org-agenda-start-on-weekday 1)
   (org-agenda-dim-blocked-tasks nil)
   (org-agenda-block-separator nil)
   (org-agenda-compact-blocks t)
   (org-agenda-skip-scheduled-if-done t)
   (org-agenda-skip-deadline-if-done t)
   (org-agenda-clockreport-parameter-plist
    (quote (:link t :maxlevel 9 :fileskip0 t :compact t :narrow 80)))))

Org protocol.

(use-package org-protocol :defer t
  :after org
  :custom (org-protocol-default-template-key "L"))

Org capturing.

(use-package org-capture :defer t
  :after org
  :preface
  (defconst afsmnghr/org-capture-templates
    '(("L" "Links" entry (file+olp+datetree afsmnghr/org-links)
       "* %c \n%U %?%:initial")
      ("d" "Diary" entry (file+olp+datetree afsmnghr/org-diary)
       "* %?\n%U\n"
       :clock-in t :jump-to-captured t)))
  :custom
  ((afsmnghr/org-diary (concat org-directory "diary.org"))
   (afsmnghr/org-links (concat org-directory "links.org"))
   (org-capture-templates afsmnghr/org-capture-templates))
  :bind
  (:map global-map ("C-c c" . org-capture)))

Timetracking.

(use-package org-clock :defer t
  :after org
  :commands org-clock-persistence-insinuate
  :custom
  ((org-clock-history-length 30)
   (org-clock-in-switch-to-state "INPROGRESS")
   (org-clock-continuously t)
   (org-clock-in-resume t)
   (org-clock-into-drawer t)
   (org-clock-out-remove-zero-time-clocks t)
   (org-clock-out-when-done t)
   (org-clock-auto-clock-resolution 'when-no-clock-is-running)
   (org-clock-persist 'history)
   (org-clock-clocked-in-display 'mode-line)
   (org-clock-persist-query-resume nil)
   (org-clock-report-include-clocking-task t))
  :config
  (org-clock-persistence-insinuate))

Notes.

(use-package deft :ensure t :defer t
  :commands deft
  :bind
  (:map global-map ("C-c d" . deft)
   :map deft-mode-map ("C-c d f" . deft-find-file))
  :custom
  ((deft-extensions '("md" "org"))
   (deft-default-extension "md")
   (deft-recursive t)
   (deft-directory (concat org-directory "notes"))
   (deft-use-filename-as-title nil)
   (deft-use-filter-string-for-filename t)
   (deft-auto-save-interval -1.0)
   (deft-file-naming-rules
     '((noslash . "-")
       (nospace . "-")
       (case-fn . downcase)))))