Last active
June 22, 2019 11:01
-
-
Save lisp-is-the-future/092d74f04376a6def7baed72a97ce6a9 to your computer and use it in GitHub Desktop.
Set up clojure dev environment in emacs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; set up clojure with emacs | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; following http://clojure-doc.org/articles/tutorials/emacs.html | |
;; install leiningen following the gist https://gist.github.com/lisp-is-the-future/4962c05d86652e7f40c0ae1a9efc2a6a | |
;;;;;;;;;;;;;;;;;;;;;;;; | |
;;;; 0. install emacs | |
;;;;;;;;;;;;;;;;;;;;;;;; | |
;; ;; in mac OS | |
;; | |
;; ;; brew install emacs --with-cocoa | |
;; ;; brew linkapps Emacs # installs latest Emacs, symlinks Emacs.app | |
;; ;; # to ~/Applications folder | |
;; ;; # emacs will be in Cellar, check by: | |
;; ;; ls /usr/local/Cellar/emacs | |
;; | |
;; ;; function to solve PATH variable problem in mac: | |
;; ;; ;; fix the PATH variable | |
;; ;; (defun set-exec-path-from-shell-PATH () | |
;; ;; (let ((path-from-shell (shell-command-to-string "TERM=vt100 $SHELL -i -c 'echo $PATH'"))) | |
;; ;; (setenv "PATH" path-from-shell) | |
;; ;; (setq exec-path (split-string path-from-shell path-separator)))) | |
;; ;; | |
;; ;; (when window-system (set-exec-path-from-shell-PATH)) | |
;; in ubuntu | |
;; ;; when installing emac globally in ubuntu: | |
;; sudo apt install emacs # or emacs24 or newer versions | |
;; ;; but I prefer local installation in ubuntu using conda: | |
;; conda create --name clj | |
;; conda activate clj | |
;; conda install -c conda-forge emacs # version v26.2 only for 64-bit | |
;; # for 32 bit, install globally | |
;;;;;;;;;;;;;;;;;;;;;;; | |
;;;; 1. setup emacs for clojure | |
;;;;;;;;;;;;;;;;;;;;;;; | |
;; create and open with any editor (I use by habit nano for such task) | |
nano ~/.emacs.d/init.el | |
;; write into it these lines: | |
(require 'package) | |
(let ((stable t)) ;; define here whether you want stable or not! | |
(add-to-list 'package-archives | |
(if stable | |
'("melpa-stable" . "http://stable.melpa.org/packages/") | |
'("melpa" . "http://melpa.org/packages/")) | |
t)) | |
(package-initialize) | |
(package-refresh-contents) ;; this is crucial yet not mentioned! | |
;; took me quite a while to figure that out! | |
;; ensures that package-archive files are up to date | |
;; if not "Packag `packagname-` is unvailable" errors | |
;; install these packages in emacs: | |
;; - clojure-mode ;; the emacs mode or editing clojure and clojurescript | |
;; - CIDER ;; REPL for clojure in emacs | |
;; - projectile ;; this is optional - for navigating swiftly in projects | |
;; either install packages one by one using `M-x package-install` | |
;; or add to | |
;; nano ~/.emacs.d/init.el | |
(defvar my-packages '(better-defaults | |
projectile | |
clojure-mode | |
cider)) | |
(dolist (p my-packages) ;; loop over list `my-packages` by assigining to var `p` | |
(unless (package-installed-p p) ;; if `p` is not installed | |
(package-install p))) ;; install it! | |
;; after adding it to ~/.emacs.d/init.el | |
;; refresh by M-x eval-buffer | |
;; however in my setting with conda environment 'clj' | |
;; it is important to start emacs by: | |
;; conda activate clj # to enter the virtual environment | |
;; emacs -q --load "~/.emacs.d/init.el" # tells explicitely which init file | |
;; # to load, `-q` means: don't load | |
;; # automatically an init file! | |
;; start emacs and send it into background | |
emacs -q --load "~/.emacs.d/init.el" & | |
;;;;;;;;;;;;;;;;;;;;;;;; | |
;; 2. how to use emacs? | |
;;;;;;;;;;;;;;;;;;;;;;;; | |
;; read/use the gist https://gist.github.com/lisp-is-the-future/b1cdefe877fbb5f61bbb4b020618a757 | |
;; to get to know minimal set of commands necessary to use emacs for lisp programming! | |
;; ignore there the common-lisp-specific commands section! | |
;; Especially moving and editing sections will be useful for getting along with emacs! | |
;;;;;;;;;;;;;;;;;;;;;;;; | |
;; 3. start a clojure project (first steps and tests in clojure) | |
;;;;;;;;;;;;;;;;;;;;;;;; | |
$ lein new <project name put here> | |
$ cd <project name put here> | |
;; e.g. | |
lein new command-line-args | |
cd command-line-args | |
;; ;; project structure is: | |
;; + doc | |
;; - intro.md | |
;; - project.clj | |
;; - README.md | |
;; + src | |
;; + command_line_args | |
;; - core.clj | |
;; + test | |
;; + command_line_args | |
;; - core_test.clj | |
;; | |
;; explanation by 'lein help tutorial' | |
;; one has to make the project plays with CIDER | |
;; for that you have to find out current verion number of cider-nrepl: | |
;; start cider by M-x cider-jack-in | |
;; in buffer at bottom you can see \[cider/cider-nrepl\"0.21.1\"] | |
;; stop cider by M-x cider-quit | |
;; (before I knew that: `(System/exit 0)` in the REPL and RET, then | |
;; C-x k to kill buffer) | |
;; close buffer by C-x k RET | |
;; then open project.clj file in emacs by: | |
;; $ emacs project.clj | |
;; and add at end of | |
(defproject command-line-args "0.1.0-SNAPSHOT" | |
:description "fIXME: write description" | |
:url "http://example.com/FIXME" | |
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" | |
:url "https://www.eclipse.org/legal/epl-2.0/"} | |
:dependencies [[org.clojure/clojure "1.10.0"]] | |
:repl-options {:init-ns command-line-args.core} ;; add here at end: | |
:profiles {:dev {:plugins [[cider/cider-nrepl "0.21.1"]]}}) | |
;; save by C-c C-s | |
;; close buffer by C-x k RET | |
;; start live repl: M-x cider-jack-in | |
;; in other window - move to other window by ` C-x o` | |
;; open 'core_test.clj' by C-x C-f test/command_line_args/core_test.clj RET | |
;; and replace test code there by: | |
(deftest pairs-of-values | |
(let [args ["--server" "localhost" | |
"--port" "8080" | |
"--environment" "production"]] | |
(is (= {:server "localhost" | |
:port "8080" | |
:environment "production"} | |
(parse-args args))))) | |
;; open there first cider REPL M-x cider-jack-in | |
;; and then jump cursor to opened file C-x o | |
;; and compile all code in file C-c C-k ;; or: M-x cider-load-buffer | |
;; compiles and loads file code to nrepl | |
;; it asks to save type: y RET | |
;; error because we did'nt define parse-args yet. | |
;; for that, open C-x C-f core.clj | |
(defn parse-args [args] | |
{}) ;; return empty dictionary | |
;; save C-x C-s | |
;; compile definition by C-c C-k | |
;; go back and do to the test and | |
;; compile the code again C-c C-k | |
;; save it C-x C-s (M-x save-buffer) | |
;; switch to test buffer C-x b RET (M-x switch-to-buffer RET) | |
;; compile it again C-c C-k | |
;; this time first success | |
;; but now run all tests C-c C-t t (M-x cider-test-run-test) | |
;; failure because of | |
;; (not (= {:server "localhost", | |
;; :port "8080", | |
;; :environment "production"} | |
;; {})) | |
;; let's redefine | |
;; switch to core.clj buffer C-x b RET (or C-x b core.clj RET) | |
(defn parse-args [args] | |
(apply hash-map args)) | |
;; compile C-c C-k | |
;; save C-x C-s | |
;; switch to test C-x b RET | |
;; compile again C-c C-k | |
;; run tests C-c C-t t | |
;; error because of | |
;; (not (= {:server "localhost", | |
;; :port "8080", | |
;; :environment "production"} | |
;; {"--port" "8080", | |
;; "--server" "localhost", | |
;; "--environment" "production"})) | |
;; keys are just strings, they have to be internalized | |
;; switch to core C-x b RET (or C-x b core.clj RET) | |
(defn parse-args [args] | |
(into {} (map (fn [[k v]] [(keywor (.replace k "--" "")) v]) | |
(partition 2 args)))) | |
;; compile C-c C-k | |
;; save C-x C-s | |
;; switch to test C-x b RET | |
;; compile again C-c C-k | |
;; run tests C-c C-t t | |
;; try with one command all tests | |
;; $ lein test # runs all tests | |
;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; 4. how to use REPL while developing | |
;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;; to open REPL for interactive mode, | |
;; let's say you are in `core.clj` | |
;; and one `*cider-repl*` is open. | |
;; let's say, you want in `parse-args` take out anaonymous function | |
;; and make out of it a named function `keywordize` | |
;; first cursor in `core.clj`, | |
;; load everything into REPL C-c C-k | |
;; split current window vertically C-x 3 or horizonatlly C-x 2 | |
;; move to REPL window C-x o | |
;; change namspace of REPL buffer C-c M-n | |
;; command-line-args.core RET | |
;; move to REPL C-x o | |
;; it needs to keep all data, so do everything in this REPL! | |
;; test that fuctions are loaded in REPL: | |
(parse-args '("key" "value")) ;; {:key "value"} ;; works! | |
;; go to core.clj C-x b | |
(defn keywordize [kvp] | |
(let [[x y].kvp] | |
[(keyword (.replace k "==" "")) v])) | |
(defn parse-args [args] | |
(intro () (map keywordize (partition 2 args)))) | |
;; re-compile file C-c C-k | |
;; or put cursor at end of function | |
;; and evaluate the s-expressions C-x C-e (M-x cider-eval-last-sexp) | |
;; both would load the functions to cider's nrepl | |
;; and in REPL - being still in its namespace - try out: | |
(keywordize ["--oh" "hai"]) ;; should return: [:oh "hai"] | |
;; clear REPL when too cluttered M-x cider-repl-clear-buffer | |
;; in REPL to get to previous commands/expressions | |
;; and later commands/expression C-arrowUp/C-arrowDown M-p/M-n | |
;; and all emacs commands for editing | |
;; also apply to REPL | |
;; in the REPL, very useful command: | |
;; show function docstring `clojure.repl/doc` | |
;; to not to have to type it fully, attach `clojure.repl` package/namespace: | |
(use 'clojure.repl) | |
;; nil | |
;; and now, let's say you wnt docstring of `println` function: | |
(doc println) | |
;; ------------------------- | |
;; clojure.core/println | |
;; ([& more]) | |
;; Same as print followed by (newline) | |
;; nil | |
;;;;; or as key shortcut C-c C-d d ;; while cursor over function name | |
;; show function source code M-. | |
;; pop stack and return to previous M-, | |
;; list all definitions in file M-x imenu ; and jump to one | |
;; kill *cider-repl* buffer M-x cider-quit | |
;; sstart another repl M-x cider-jack-in | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment