Skip to content

Instantly share code, notes, and snippets.

@vishesh
Last active December 3, 2024 03:40
Show Gist options
  • Select an option

  • Save vishesh/8aa9dec6b0097b9c5e14688638831c6a to your computer and use it in GitHub Desktop.

Select an option

Save vishesh/8aa9dec6b0097b9c5e14688638831c6a to your computer and use it in GitHub Desktop.
Leanest usable emacs config, without using any external packages in emacs29
;;; init.el --- Emacs configuration. -*- lexical-binding: t -*-
;;; Copyright (C) 2023 Vishesh Yadav
;;; Author: Vishesh Yadav <visehsh3y@gmail.com>
;;
;; Leanest usable emacs config, without using any external packages in emacs29.
;;;----------------------------------------------------------------------------
;;; Personal
;;;----------------------------------------------------------------------------
(setq user-full-name "Vishesh Yadav")
(setq user-mail-address "vishesh3y@gmail.com")
;;;----------------------------------------------------------------------------
;;; Constants
;;;----------------------------------------------------------------------------
(when (< emacs-major-version 27)
(error "This requires Emacs 27.0 and above!"))
;;;----------------------------------------------------------------------------
;;; Setup Package Management
;;;----------------------------------------------------------------------------
;; -> Install `'use-package`.
(require 'package)
(setq package-enable-at-startup nil)
(setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "http://melpa.org/packages/")))
(package-initialize)
;; Setup `use-package'
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
;; Should set before loading `use-package'
(eval-and-compile
(setq byte-compile-warnings '(cl-functions))
(setq use-package-always-ensure t)
(setq use-package-always-defer t)
(setq use-package-expand-minimally t))
(eval-when-compile
(require 'use-package))
;; Load path
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
(add-to-list 'load-path (expand-file-name "site-lisp" user-emacs-directory))
(setq custom-theme-directory (expand-file-name "themes" user-emacs-directory))
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file 'no-error)
;;;----------------------------------------------------------------------------
;;; Editing
;;;----------------------------------------------------------------------------
(prefer-coding-system 'utf-8)
;; Tab and Space
(global-display-fill-column-indicator-mode)
(setq-default major-mode 'text-mode
c-basic-offset 4
fill-column 80
tab-width 4
indent-tabs-mode nil)
;; Close brackets/quotes automatically.
(electric-pair-mode +1)
(setq kill-ring-max 500)
;; Save clipboard contents into kill-ring before replace them
(setq save-interprogram-paste-before-kill t)
;; Repeating C-SPC after popping mark pops it again
(setq set-mark-command-repeat-pop t)
;; Don't wrap lines.
(setq-default truncate-lines t)
;; Delete selection if you insert
(use-package delsel
:hook (after-init . delete-selection-mode))
;; Rectangle
(use-package rect
:ensure nil
:bind (("<C-return>" . rectangle-mark-mode)))
(bind-key "C-a" 'back-to-indentation-or-beginning-of-line)
(bind-key "M-g" 'goto-line)
(bind-key "C-M-'" 'match-paren)
(bind-key "C-w" 'kill-region-or-backward-word)
(bind-key "M-w" 'kill-region-or-thing-at-point)
(bind-key "C-S-k" 'kill-whole-line)
(bind-key "C-o" 'open-line-below)
(bind-key "C-c C-o" 'open-line-above)
(bind-key "C-S-o" 'open-line-above)
(bind-key "M-j" 'join-line)
(bind-key "M-_" 'previous-buffer)
(bind-key "M-+" 'next-buffer)
(bind-key "C-x C-b" 'switch-to-buffer)
(bind-key "C-6" 'global-display-line-numbers-mode)
(bind-key "M-s i" 'completion-at-point)
(bind-key "M-\\" 'imenu)
(bind-key "C-c g" 'grep-completing-read)
(bind-key "C-c C-g" 'rgrep)
(bind-keys ("C-M->" . indent-rigidly-right)
("C-M-<" . indent-rigidly-left))
(use-package ibuffer :bind (("C-x b" . ibuffer-bs-show)))
(setq hippie-expand-try-functions-list
'(try-expand-dabbrev
try-expand-dabbrev-all-buffers
try-expand-dabbrev-from-kill
try-complete-file-name-partially
try-complete-file-name try-expand-all-abbrevs
try-expand-list try-expand-line try-complete-lisp-symbol-partially
try-complete-lisp-symbol))
(bind-key "M-/" 'hippie-expand)
;; Click to browse URL or to send to e-mail address
(use-package goto-addr
:ensure nil
:hook ((text-mode . goto-address-mode)
(prog-mode . goto-address-prog-mode)))
;;;----------------------------------------------------------------------------
;;; Search
;;;----------------------------------------------------------------------------
;; Make backspace delete the search term not goto last address
(define-key isearch-mode-map [remap isearch-delete-char] 'isearch-del-char)
(defun isearchp-kill-ring-save ()
"Copy the current search string to the kill ring. Credits: isearch+"
(interactive)
(kill-new isearch-string)
(sit-for 1)
(isearch-update))
;; Default query-replace shortcut on macOS would conflict with system shortcut
;; to take screenshot. So lets rebind these.
(bind-key "C-?" #'query-replace)
(bind-keys :map isearch-mode-map
("C-?" . isearch-query-replace)
("M-w" . isearchp-kill-ring-save))
(defcustom vyconfig-use-ripgrep nil
"Use ripgrep for completing-read search."
:group 'vyconfig
:type 'boolean)
(defun grep-completing-read ()
"Do a project search using completing-read"
(interactive)
(let* ((default-term (if (region-active-p)
(substring-no-properties
(buffer-substring (mark) (point)))
(thing-at-point 'symbol)))
(term (read-string "search for: " default-term))
(execute-search
(lambda (term)
(cond
((and vyconfig-use-ripgrep (executable-find "rg"))
;; FIXME: Takes regexp, making it awkward for terms with special
;; chars.
(process-lines "rg" "--line-number -e" term))
((and (project-current nil)
(equal (cadr (project-current nil)) 'Git))
(process-lines "git" "grep" "-niH" "-e" term))
(t (process-lines "grep" "-nirH" "-e" term)))))
(results (funcall execute-search term))
(line-list (split-string (completing-read "results: " results) ":"))
(rfile (car line-list))
(rlnum (string-to-number (car (cdr line-list)))))
(find-file rfile)
(goto-line rlnum)
(recenter)))
;;;----------------------------------------------------------------------------
;;; Programming
;;;----------------------------------------------------------------------------
(bind-keys
("C-c c" . recompile)
("<f12>" . compile))
(bind-key "M-;" 'comment-dwim)
(bind-key "C-7" 'comment-line)
(use-package cc-mode
:bind (:map c-mode-base-map
("C-c o" . eglot-clangd-find-other-file)))
;;;----------------------------------------------------------------------------
;;; Project
;;;----------------------------------------------------------------------------
(use-package project
:bind (("C-c M-k" . project-kill-buffers)
("C-c m" . project-compile)
("C-x f" . find-file)
("C-t" . find-file-in-project-or-directory))
:bind-keymap ("C-c p" . project-prefix-map)
:defines find-file-in-project-or-directory
:custom
(project-switch-commands
'((project-find-file "Find file")
(project-find-regexp "Grep" ?h)))
:config
(defun find-file-in-project-or-directory ()
(interactive)
(if (project-current nil)
(project-find-file)
(call-interactively #'find-name-dired))))
(use-package vc
:bind
(("C-c i" . vc-git-grep)
("C-x g" . vc-dir-root))
:config
(defun vc-git-expanded-log-entry (revision)
"Just adds commit stats to expanded commit entry in log-view."
(with-temp-buffer
(apply #'vc-git-command t nil nil
(list "log" revision "-1" "--stat" "--no-color" "--stat" "--"))
(goto-char (point-min))
(unless (eobp)
;; Indent the expanded log entry.
(while (re-search-forward "^" nil t)
(replace-match " ")
(forward-line))
(concat "\n" (buffer-string))))))
;;;----------------------------------------------------------------------------
;;; Diffing
;;;----------------------------------------------------------------------------
;; A comprehensive visual interface to diff & patch
(use-package ediff
:hook(;; show org ediffs unfolded
(ediff-prepare-buffer . outline-show-all)
;; restore window layout when done
(ediff-quit . winner-undo))
:config
(setq ediff-window-setup-function 'ediff-setup-windows-plain)
(setq ediff-split-window-function 'split-window-horizontally)
(setq ediff-merge-split-window-function 'split-window-horizontally))
;;;----------------------------------------------------------------------------
;;; Large files
;;;----------------------------------------------------------------------------
(defun fast-file-view-mode ()
"Makes the buffer readonly and disables fontlock and other bells and whistles
for faster viewing"
(interactive)
(setq buffer-read-only t)
(buffer-disable-undo)
(fundamental-mode)
(font-lock-mode -1)
(when (boundp 'anzu-mode)
(anzu-mode -1)))
(defun large-find-file-hook ()
"If a file is over a given size, make the buffer read only."
(when (> (buffer-size) (* 1024 1024))
(fast-file-view-mode)))
(add-hook 'find-file-hook 'large-find-file-hook)
;;;----------------------------------------------------------------------------
;;; Window Management
;;;----------------------------------------------------------------------------
(bind-key "C-q" 'other-window)
;; Directional window-selection routines
(use-package windmove
:ensure nil
:hook (after-init . windmove-default-keybindings))
;; Restore old window configurations
(use-package winner
:ensure nil
:hook (after-init . winner-mode)
:init (setq winner-boring-buffers '("*Completions*"
"*Compile-Log*"
"*inferior-lisp*"
"*Fuzzy Completions*"
"*Apropos*"
"*Help*"
"*cvs*"
"*Buffer List*"
"*Ibuffer*"
"*esh command on file*")))
;;;----------------------------------------------------------------------------
;;; Buffers and Files
;;;----------------------------------------------------------------------------
(setq delete-by-moving-to-trash t) ; Deleting files go to OS's trash folder
(setq make-backup-files nil) ; Forbide to make backup files
(setq auto-save-default nil) ; Disable auto save
(use-package autorevert
:diminish auto-revert-mode
:hook (after-init . global-auto-revert-mode))
(use-package saveplace
:hook (after-init . save-place-mode))
(use-package recentf
;; lazy load recentf
:hook (find-file . (lambda () (unless recentf-mode
(recentf-mode)
(recentf-track-opened-file))))
:bind ("C-c <RET>" . recentf)
:init
(add-hook 'after-init-hook #'recentf-mode)
(setq recentf-max-saved-items 200)
:config
(add-to-list 'recentf-exclude (expand-file-name package-user-dir))
(add-to-list 'recentf-exclude ".objs")
(add-to-list 'recentf-exclude ".cache")
(add-to-list 'recentf-exclude ".cask")
(add-to-list 'recentf-exclude "bookmarks")
(add-to-list 'recentf-exclude "COMMIT_EDITMSG\\'"))
(use-package savehist
:hook (after-init . savehist-mode)
:init (setq enable-recursive-minibuffers t ; Allow commands in minibuffers
history-length 1000
savehist-additional-variables '(mark-ring
global-mark-ring
search-ring
regexp-search-ring
extended-command-history)
savehist-autosave-interval 60))
;;;----------------------------------------------------------------------------
;;; GUI/CLI/Frame/Look and Feel/System
;;;----------------------------------------------------------------------------
(when (display-graphic-p)
(add-to-list 'default-frame-alist '(height . 60))
(add-to-list 'default-frame-alist '(width . 100)))
(make-face-bold 'font-lock-keyword-face)
;;; Tweak modifier keys per platform.
;; OS specific tweaks
(cond
((string-equal system-type "darwin")
(setq ns-use-thin-smoothing t)
(setq mac-option-key-is-meta t
mac-command-key-is-meta nil
mac-command-modifier 'meta
mac-option-modifier 'super
ns-use-native-fullscreen nil))
((string-equal system-type "gnu/linux") t))
(when (display-graphic-p)
(bind-keys ([(control f11)] . toggle-frame-fullscreen)))
;; Load remote environment when using tramp-mode.
(eval-after-load 'tramp '(setenv "SHELL" "/bin/bash"))
;;; Remove some GUI features.
(menu-bar-mode -1)
(when window-system
(tool-bar-mode -1)
(scroll-bar-mode -1))
(setq use-file-dialog nil
use-dialog-box nil
inhibit-startup-screen t
inhibit-startup-echo-area-message t)
(global-unset-key (kbd "<C-wheel-down>"))
(global-unset-key (kbd "<C-wheel-up>"))
;; Secondary click open context menu in GUI.
(context-menu-mode)
;; Menubar in terminal.
(bind-key "M-`" #'tmm-menubar)
;; Ask yes or no, explicitely.
(fset 'yes-or-no-p 'y-or-n-p)
(bind-key "C-<tab>" 'other-frame)
;; Show column numbers in mode line.
(setq column-number-mode t)
;; Go directly to scratch buffer.
(setq inhibit-splash-screen t)
;; For Mac OSX weird redraw bug on bell
(setq ring-bell-function (lambda () (message "*woop*")))
;; Compilation output, autoscroll
(setq compilation-scroll-output t)
;; Line Numbers in gutter.
(use-package display-line-numbers
:hook ((prog-mode . display-line-numbers-mode)
(prog-mode . which-function-mode)
(markdown-mode . display-line-numbers-mode)
(text-mode . display-line-numbers-mode)))
;; Show path if names are same
(setq uniquify-buffer-name-style 'post-forward-angle-brackets)
;;; Scrolling
(when (and window-system (>= emacs-major-version 29))
(pixel-scroll-precision-mode 1))
(setq scroll-preserve-screen-position 'always
mouse-wheel-scroll-amount '(1 ((shift) . 1)) ; Scroll one line at a time.
mouse-wheel-progressive-speed nil
scroll-step 1
scroll-margin 1
scroll-conservatively 10000)
(setq visible-bell t
inhibit-compacting-font-caches t ; Don’t compact font caches during GC.
adaptive-fill-regexp "[ t]+|[ t]*([0-9]+.|*+)[ t]*"
adaptive-fill-first-line-regexp "^* *$"
sentence-end "\\([。!?]\\|……\\|[.?!][]\"')}]*\\($\\|[ \t]\\)\\)[ \t\n]*"
sentence-end-double-space nil)
;; Never kill scratch.
(with-current-buffer "*scratch*"
(emacs-lock-mode 'kill))
;; Don't show native comp warnings.
(setq native-comp-async-report-warnings-errors 'silent)
;; Enable colors in compilation output.
;; HACK: Don't apply on grep-mode.
(use-package ansi-color
:config
(defun --compilation-filter (fn proc string)
"Wrap `compilation-filter' (FN PROC STRING) to support `ansi-color'."
(let ((buf (process-buffer proc)))
(when (buffer-live-p buf)
(with-current-buffer buf
(funcall fn proc string)
(when (not (equal major-mode 'grep-mode))
(let ((inhibit-read-only t))
(ansi-color-apply-on-region (point-min) (point-max))))))))
(advice-add 'compilation-filter :around #'--compilation-filter))
;;;----------------------------------------------------------------------------
;;; Completions
;;;----------------------------------------------------------------------------
(setq tab-always-indent 'complete)
;; For making completion-at-point work with icomplete/fido.
;; https://www.reddit.com/r/emacs/comments/ulsrb5/what_have_you_recently_removed_from_your_emacs/
(defun completing-read-at-point (start end col &optional pred)
(if (minibufferp) (completion--in-region start end col pred)
(let* ((init (buffer-substring-no-properties start end))
(all (completion-all-completions init col pred (length init)))
(completion (cond
((atom all) nil)
((and (consp all) (atom (cdr all))) (car all))
(t (completing-read "Completions: " col pred t init)))))
(if completion
(progn
(delete-region start end)
(insert completion)
t)
(message "No completions") nil))))
(setq completion-category-defaults nil)
(setq completion-in-region-function #'completing-read-at-point)
(use-package minibuffer
:ensure nil
:bind (:map minibuffer-mode-map
("C-n" . minibuffer-next-completion)
("C-p" . minibuffer-previous-completion)
("M-<RET>" . my/minibuffer-choose-completion))
:bind (:map completion-in-region-mode-map
("C-n" . minibuffer-next-completion)
("C-p" . minibuffer-previous-completion)
("M-<RET>" . my/minibuffer-choose-completion))
:init
(setq enable-recursive-minibuffers t
completion-styles '(initials partial-completion flex)
completions-format 'one-column
completions-header-format nil
completion-auto-help t
completion-show-help nil
completions-max-height 10
resize-mini-windows t
completion-flex-nospace nil
completion-pcm-complete-word-inserts-delimiters t
completion-auto-select nil
completion-ignore-case t
read-buffer-completion-ignore-case t
read-file-name-completion-ignore-case t)
(defun my/minibuffer-choose-completion (&optional no-exit no-quit)
(interactive "P")
(with-minibuffer-completions-window
(let ((completion-use-base-affixes nil))
(choose-completion nil no-exit no-quit)))))
(use-package icomplete
:bind (:map icomplete-fido-mode-map
("TAB" . icomplete-force-complete)
("C-j" . icomplete-ret)
("C-n" . icomplete-forward-completions)
("C-p" . icomplete-backward-completions))
:hook (icomplete-minibuffer-setup . (lambda () (setq-local truncate-lines t)))
:init
(setq icomplete-in-buffer t
icomplete-with-completion-tables t
icomplete-hide-common-prefix nil
icomplete-prospects-height 6
icomplete-compute-delay 0)
(fido-mode +1)
(fido-vertical-mode +1))
;; LSP
(use-package eglot
:hook ((c++-mode . eglot-ensure)
(c-mode . eglot-ensure)
(python-mode . eglot-ensure))
:bind (:map eglot-mode-map
("C-c a r" . #'eglot-rename)
("C-c h" . #'eldoc)
("C-<down-mouse-1>" . #'xref-find-definitions)
("C-S-<down-mouse-1>". #'xref-find-references)
("C-c C-c" . #'eglot-code-actions)
("C-M-<tab>" . #'eglot-format))
:config
(defun eglot-clangd-find-other-file (&optional new-window)
"Switch between the corresponding C/C++ source and header file."
(interactive)
(let* ((server (eglot--current-server-or-lose))
(rep
(jsonrpc-request
server
:textDocument/switchSourceHeader
(eglot--TextDocumentIdentifier))))
(unless (equal rep nil)
(funcall #'find-file (eglot--uri-to-path rep)))))
(with-eval-after-load 'eglot
(setq --my-clangd-args '("clangd"
"-j=4"
"--background-index"
"--clang-tidy"
"--all-scopes-completion"
"--completion-style=detailed"
"--header-insertion=iwyu"
"--header-insertion-decorators=0"))
(add-to-list 'eglot-server-programs `(c-mode . ,--my-clangd-args))
(add-to-list 'eglot-server-programs `(c++-mode . ,--my-clangd-args)))
(setq eldoc-echo-area-use-multiline-p nil
eglot-extend-to-xref t
eldoc-idle-delay 0.1
eglot-autoshutdown t))
(use-package flymake
:bind (("C-c l a" . flymake-show-project-diagnostics)
("C-c l b" . flymake-show-buffer-diagnostics)
("C-x ]" . flymake-goto-next-error)
("C-x [" . flymake-goto-prev-error)))
(use-package flyspell
:hook (text-mode . flyspell-mode))
;;;----------------------------------------------------------------------------
;;; Highlight
;;;----------------------------------------------------------------------------
(use-package hi-lock
:bind
(("M-n" . isearch-forward-symbol-at-point)
("M-l" . my/toggle-mark-symbol-at-point))
:config
(defun my/toggle-mark-symbol-at-point ()
(interactive)
(if hi-lock-interactive-patterns
(unhighlight-regexp (car (car hi-lock-interactive-patterns)))
(highlight-symbol-at-point))))
(use-package hl-line
:hook (after-init . global-hl-line-mode))
(use-package paren
:hook (after-init . show-paren-mode)
:config
(setq show-paren-when-point-inside-paren t
show-paren-when-point-in-periphery t))
;; Visualize TAB, (HARD) SPACE, NEWLINE
(use-package whitespace
:ensure nil
:bind ("C-x w" . whitespace-mode)
:hook ((prog-mode outline-mode conf-mode) . whitespace-mode)
:config
(setq whitespace-line-column fill-column) ;; limit line length
;; Automatically clean up bad whitespace
(setq whitespace-action '(auto-cleanup))
;; Only show bad whitespace
(setq whitespace-style '(face trailing indentation empty)))
;; Pulse current line
(use-package pulse
:ensure nil
:preface
(defun my-pulse-momentary (&rest _)
"Pulse the current line."
(pulse-momentary-highlight-one-line (point) 'next-error))
(defun my-recenter (&rest _)
"Recenter and pulse the current line."
(recenter)
(my-pulse-momentary))
:hook ((bookmark-after-jump next-error other-window
dumb-jump-after-jump imenu-after-jump) . my-recenter)
:init (dolist (cmd '(recenter-top-bottom
other-window windmove-do-window-select
pop-to-mark-command pop-global-mark
pager-page-down pager-page-up))
(advice-add cmd :after #'my-pulse-momentary)))
;;;----------------------------------------------------------------------------
;;; Utils
;;;----------------------------------------------------------------------------
(use-package dired
:ensure nil
:bind (:map dired-mode-map
("C-c C-p" . wdired-change-to-wdired-mode))
:hook ((dired-mode . (lambda () (local-unset-key (kbd "C-t"))))) ; image-dired
:config
(setq dired-recursive-deletes 'always
dired-recursive-copies 'always
dired-isearch-filenames 'dwim)
(when (executable-find "gls")
;; Use GNU ls when possible.
(setq dired-use-ls-dired nil)
(setq ls-lisp-use-insert-directory-program t)
(setq insert-directory-program "gls")
(setq dired-listing-switches "-alh --group-directories-first"))
(use-package dired-aux :ensure nil))
;;;----------------------------------------------------------------------------
;;; Library
;;;----------------------------------------------------------------------------
(defun match-paren (&optional arg)
"Go to the matching parenthesis character if one is adjacent to point."
(interactive "^p")
(cond ((looking-at "\\s(") (forward-sexp arg))
((looking-back "\\s)" 1) (backward-sexp arg))
;; Now, try to succeed from inside of a bracket
((looking-at "\\s)") (forward-char) (backward-sexp arg))
((looking-back "\\s(" 1) (backward-char) (forward-sexp arg))))
(defun kill-region-or-backward-word ()
"If the region is active and non-empty, call `kill-region'.
Otherwise, call `backward-kill-word'."
(interactive)
(call-interactively
(if (use-region-p) 'kill-region 'backward-kill-word)))
(defun open-line-below ()
(interactive)
(move-end-of-line 1)
(newline)
(indent-according-to-mode))
(defun open-line-above ()
(interactive)
(move-beginning-of-line 1)
(newline)
(forward-line -1)
(indent-according-to-mode))
(defun kill-region-or-thing-at-point (beg end)
(interactive "r")
(unless (region-active-p)
(save-excursion
(setq beg (re-search-backward "\\_<" nil t))
(setq end (re-search-forward "\\_>" nil t))))
(kill-ring-save beg end))
(defun back-to-indentation-or-beginning-of-line ()
(interactive)
(if (= (point) (save-excursion (back-to-indentation) (point)))
(beginning-of-line)
(back-to-indentation)))
(defun find-file-at-point-with-line()
"Goto file at point, and move cursor to line number if given."
(interactive)
(setq line-num 0)
(save-excursion
(search-forward-regexp "[^ ]:" (point-max) t)
(if (looking-at "[0-9]+")
(setq line-num
(string-to-number
(buffer-substring (match-beginning 0) (match-end 0))))))
(find-file (ffap-guesser))
(if (not (equal line-num 0))
(goto-line line-num)))
;;;----------------------------------------------------------------------------
;;; Unorganized
;;;----------------------------------------------------------------------------
;; For local configurations.
(load (expand-file-name "local.el" user-emacs-directory) 'noerror)
(define-minor-mode minor-mode-blackout-mode
"Hides minor modes from the mode line."
t)
(catch 'done
(mapc (lambda (x)
(when (and (consp x)
(equal (cadr x) '("" minor-mode-alist)))
(let ((original (copy-sequence x)))
(setcar x 'minor-mode-blackout-mode)
(setcdr x (list "" original)))
(throw 'done t)))
mode-line-modes))
(defun timestamp ()
(interactive)
(insert (format-time-string "%Y-%M-%d %I:%M:%S %Z")))
;;------------------------------------------------------------------------------
(provide 'init)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment