hristoast/site/editing-with-emacs-python-part-2.html -rw-r--r-- 13.5 KiB View raw
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
<h1 id="title">Editing with Emacs: Python - Part 2</h1>

<div id="dates">
  <span>Posted: <time id="post-date">2016-09-29</time></span>
</div>

<p>
  This is a follow-up to <a href="https://hristos.triantafillou.us/blog/2015/09/30/editing-python-emacs/">my earlier post about editing python with Emacs</a>, as well as the start of a series on using Emacs effectively with a number of languages.

  I'll go into what I use for editing python with Emacs and why, as well as how I set it all up. Let's go!
</p>

<p>
  If you want the satisfaction of putting together a fine python editor, that is also capable of serving as a sophisticated editor for many many other languages, with the added benefit of having at least some control over how it all works, then read on! You just might learn something ...
</p>

<div id="toc"></div>

<h3 class="bold" id="goals">Goals</h3>

<p>
  Before I get into too many specifics, let's look at what we actually want to achieve here:
</p>

<ul>
  <li>Syntax highlighting</li>
  <li>Syntax checking</li>
  <li>Code completion</li>
  <li>Support multiple Python versions</li>
  <li>Initiate a build from within Emacs</li>
</ul>

<p>
  These things are already provided out of the box by <a href="https://www.jetbrains.com/pycharm/">other editors/IDEs</a> and frankly some of them do it quite well. So why bother?
</p>

<p>
  As with many things that I do you really must care about DIY to get the most out of it; if you're looking to just edit python efficiently as quickly as possible, then this post might not be for you.
</p>

<h3 class="bold" id="quick-note-about-emacs-packages">A quick note about installing Emacs packages ...</h3>

<p>
  I use, greatly prefer, and strongly recommend John Weigley's excellent <a href="https://github.com/jwiegley/use-package/"><code>use-package</code></a> macro for installing and managing my Emacs packages.  It helps me keep configurations clean, prevents Emacs from loading all-the-things, all the time, and just feels better than a massive cluttered <code>init.el</code>.
</p>
<p>
  Of course, it's 100% optional, but this and every other one of my writings will be written as a <code>use-package</code> user! Keep that in mind as you read on, as the below Emacs-lisp snippets assume you are one too!
</p>

<h3 class="bold" id="syntax-highlighting">Syntax highlighting</h3>

<p>
  This one's debatable; There are several pieces I've read that suggest the author prefers and is more productive with the coloring off <sup><a href="https://www.robertmelton.com/2016/04/10/syntax-highlighting-off/">[1]</a> <a href="http://www.linusakesson.net/programming/syntaxhighlighting/">[2]</a></sup>.  I won't editorialize here, skip ahead if need be.
</p>

<p>
  This one's also subjective.  The general look and coloring of your editor is something that belongs totally to you, and you should spend some time checking out your options.  MELPA has a ton of themes - start there! I personally use the <a href="https://github.com/andre-richter/emacs-lush-theme">Lush theme</a> after going through most if not all of the themes on MELPA at some point or another.
</p>

<h3 class="bold" id="syntax-checking">Syntax checking</h3>

<p>
  With syntax checking, you'll receive warning and error notifications in-line in your code - updated when you save your file.
</p>

<h4 id="flycheck">Flycheck</h4>

<p>
  Flycheck is a general syntax checker for Emacs.  Just by installing it, you will not only get your desired python syntax checking - you'll get it for a host of other languages as well <sup><a href="http://www.flycheck.org/">[3]</a></sup>.  One of the single most useful packages for Emacs, in my opinion.
</p>

<p>
  The following snippet will get Flycheck installed and set up with some handy yet optional keybindings:
</p>

<pre><code>(use-package flycheck
  :ensure t
  :bind
  (("C-c e n" . flycheck-next-error)
   ("C-c e p" . flycheck-previous-error))
  :config
  (add-hook 'after-init-hook #'global-flycheck-mode))</code></pre>

<h3 class="bold" id="code-completion">Code completion</h3>

<p>
  Code completion is another feature that some folks don't care for; I find it extremely useful, and it is supported quite well with python in Emacs once you get a few packages to handle the task.
</p>

<h4 id="company-mode">Company mode</h4>

<p>
  Company is a general completion framework for Emacs, and is compatible with many languages via backends <sup><a href="https://company-mode.github.io/">[4]</a></sup>.  It should be perfectly usable out of the box, but I've made a few tweaks that you can apply with the below snippet:
</p>

<pre><code>(use-package company
  :ensure t
  :config
  (add-hook 'after-init-hook 'global-company-mode)
  (setq
    company-echo-delay 0
    company-idle-delay 0.2
    company-minimum-prefix-length 1
    company-tooltip-align-annotations t
    company-tooltip-limit 20)
    ;; Default colors are awful - borrowed these from gocode (thanks!):
    ;; https://github.com/nsf/gocode/tree/master/emacs-company#color-customization
    (set-face-attribute
     'company-preview nil :foreground "black" :underline t)
    (set-face-attribute
     'company-preview-common nil :inherit 'company-preview)
    (set-face-attribute
     'company-tooltip nil :background "lightgray" :foreground "black")
    (set-face-attribute
     'company-tooltip-selection nil :background "steelblue" :foreground "white")
    (set-face-attribute
     'company-tooltip-common nil :foreground "darkgreen" :weight 'bold)
    (set-face-attribute
     'company-tooltip-common-selection nil :foreground "black" :weight 'bold))</code></pre>

<p>
  Although the <code>(add-hook)</code> call is necessary for the functionality we want, the above <code>(setq)</code>'d variables are optional; feel free to tweak them to your liking.
</p>

<h4 id="jedi">Jedi</h4>

<p>
  The last piece we need is <a href="https://github.com/davidhalter/jedi">Jedi</a>, which provides rich autocmplete and static analysis (to compliment Flycheck.) First, make sure you've got <code>virtualenv</code> installed.  I don't use this, but Jedi wants it.  On Void Linux:
</p>

<pre><code># xbps-install python-virtualenv</code></pre>

<p>
  The <code>#</code> at the start means this should be run with elevated privileges.  Note that the process for installing virtualenv may vary on your OS.
</p>

<p>
  Now, use <code>pip</code> to install some packages that will be needed:
</p>

<pre><code>$ pip install flake8 jedi</code></pre>

<p>
  If you've got Python installed via <code>pyenv</code>, then <code>pip</code> can be ran as your normal user, without elevated privileges.  Hence the <code>$</code> at the start of the above snippte.  Note that system-installed <code>pip</code>s may require elevated privileges to install these.
</p>

<p>
  Per the Jedi readme <sup><a href="https://github.com/tkf/emacs-jedi/#company-users">[5]</a></sup>, we won't be using the <code>jedi</code> package.  We'll instead use the <code>company-jedi</code> package to add jedi as a company backend.  Add this to your <code>init.el</code>:
</p>

<pre><code>(use-package company-jedi
  :defer t
  :ensure t
  :init
  (setq jedi:complete-on-dot t)
  (setq jedi:get-in-function-call-delay 0.2))</code></pre>

<p>
  This will ensure you've got <code>company-jedi</code>, as well as configure Jedi a bit.  Now we just need to hook it into <code>python-mode</code>:
</p>

<pre><code>(use-package python-mode
  :bind
  ("&lt;S-down-mouse-1&gt;" . goto-definition-at-point)
  ("&lt;S-down-mouse-3&gt;" . quick-pydoc)
  :functions jedi:goto-definition
  :init
  (setq-default python-shell-completion-native-enable nil)

  (defun goto-definition-at-point (event)
    "Move the point to the clicked position
     and jedi:goto-definition the thing at point."
    (interactive "e")
    (let ((es (event-start event)))
      (select-window (posn-window es))
      (goto-char (posn-point es))
      (jedi:goto-definition)))

  (defun quick-pydoc (event)
    "Move the point to the clicked position
     and pydoc the thing at point."
    (interactive "e")
    (let ((es (event-start event)))
      (select-window (posn-window es))
      (goto-char (posn-point es))
      (pydoc-at-point)))

  (add-hook 'python-mode-hook
            (lambda ()
              (when (derived-mode-p 'python-mode)
                (add-to-list 'company-backends 'company-jedi)))))</code></pre>

<p>
  This gives us a few things:
</p>

<ol>
  <li>Shift-left click to go to the definition of what's at point.</li>
  <li>Shift-right click to get the Python documentation for what's at point.</li>
  <li>When <code>python-mode</code> starts, it'll also start up Jedi.el (our auto-complete.)</li>
</ol>

<h3 class="bold" id="support-multiple-pythons">Support multiple Python versions</h3>

<p>
  Jedi.el is now set up, but it would be great if one could easily swap between Python versions installed with pyenv.  This can be achieved by adding a few functions.  The updated <code>python-mode</code> section now looks like:
</p>

<pre><code>(use-package python-mode
  :bind
  ("&lt;S-down-mouse-1&gt;" . goto-definition-at-point)
  ("&lt;S-down-mouse-3&gt;" . quick-pydoc)
  :functions jedi:goto-definition jedi:stop-server maybe-stop-jedi-server
  :init
  (setq-default python-shell-completion-native-enable nil)

  (defun goto-definition-at-point (event)
    "Move the point to the clicked position
     and jedi:goto-definition the thing at point."
    (interactive "e")
    (let ((es (event-start event)))
      (select-window (posn-window es))
      (goto-char (posn-point es))
      (jedi:goto-definition)))

  (defun maybe-stop-jedi-server ()
    "Stop the Jedi server, if need me."
    (if (boundp 'jedi:stop-server)
        (jedi:stop-server)))

  (defun quick-pydoc (event)
    "Move the point to the clicked position
     and pydoc the thing at point."
    (interactive "e")
    (let ((es (event-start event)))
      (select-window (posn-window es))
      (goto-char (posn-point es))
      (pydoc-at-point)))

  (defun use-pyenv352 ()
    "Configure Jedi to use a pyenv-provided Python 3.5.2."
    (interactive)
    (maybe-stop-jedi-server)
    (let ((pyenv352 (concat my-home "/.pyenv/versions/3.5.2")))
      (setq
       jedi:environment-virtualenv (list (concat pyenv352 "/bin/pyvenv-3.5"))
       jedi:environment-root (concat dot-emacs "/.py/352")
       jedi:server-args
       '("--sys-path" "~/.pyenv/versions/3.5.2/lib/python3.5/site-packages"))
      (if (not (file-exists-p
                (concat jedi:environment-root
                        "/lib/python3.5/site-packages/jediepcserver.py")))
          (jedi:install-server))))

  (defun use-pyenv2712 ()
    "Configure Jedi to use a pyenv-provided Python 2.7.12."
    (interactive)
    (maybe-stop-jedi-server)
    (let ((pyenv2712 (concat my-home "/.pyenv/versions/2.7.12")))
      (setq
       jedi:environment-virtualenv (list (concat pyenv2712 "/bin/virtualenv"))
       jedi:environment-root (concat dot-emacs "/.py/2712")
       jedi:server-args
       '("--sys-path" "~/.pyenv/versions/2.7.12/lib/python2.7/site-packages"))
      (if (not (file-exists-p
                (concat jedi:environment-root
                        "/lib/python2.7/site-packages/jediepcserver.py")))
          (jedi:install-server))))

  (add-hook 'python-mode-hook 'use-pyenv352)
  (add-hook 'python-mode-hook
            (lambda ()
              (when (derived-mode-p 'python-mode)
                (add-to-list 'company-backends 'company-jedi)))))</code></pre>

<p>
  And these defined earlier in the file:
</p>

<pre><code>(defconst dot-emacs "~/.emacs.d")
(defconst my-home (getenv "HOME"))
(defconst my-bin (concat my-home "/bin"))
(defconst my-src (concat my-home "/src"))</code></pre>

<p>
  This enables a few things:
</p>

<ol>
  <li>The jedi server will be automatically installed the first time a python file is opened, if it is not already.</li>
  <li>Python versions can be switched, on the fly, with a simple <code>M-x use-pyenvN</code>.</li>
</ol>

<p>
  Now we've got all features available to any Python version we need.
</p>

<h3 class="bold" id="build-from-within-emacs">Initiate a build from within Emacs</h3>

<p>
  I love <code>Makefile</code>s, and in this case they are a handy tool for hooking build capabilities into Emacs.  Given the following helper function and binding in your <code>init.el</code>:
</p>

<pre><code>(defun build-project ()
  "Compile the current project."
  (interactive)
  (setq-local compilation-read-command nil)
  (call-interactively 'compile))

(global-set-key (kbd "&lt;f5&gt;") 'build-project)</code></pre>

<p>
  ... and a properly-configured <code>Makefile</code>, you can simply hit F5 within Emacs to fire off your build (or do any other arbitrary task!)
</p>

<h3 class="bold" id="conclusion">Conclusion</h3>

<p>
  This is technically a "repost" for me, but I really wanted to go into more detail and correct some mistakes/oversights from my last post - an errata of sorts but more of a part deux.
</p>

<p>
  All of the above and more are fully laid out in my personal <code>init.el</code>, so be sure to check that out.  I plan to put up a few asciinema clips of all this in action, so stay tuned...
</p>

<h3 class="bold" id="references">References</h3>

<ol>
  <li><a href="https://www.robertmelton.com/2016/04/10/syntax-highlighting-off/">https://www.robertmelton.com/2016/04/10/syntax-highlighting-off/</a></li>
  <li><a href="http://www.linusakesson.net/programming/syntaxhighlighting">http://www.linusakesson.net/programming/syntaxhighlighting</a></li>
  <li><a href="http://www.flycheck.org/">http://www.flycheck.org/</a></li>
  <li><a href="https://company-mode.github.io/">https://company-mode.github.io/</a></li>
  <li><a href="https://github.com/tkf/emacs-jedi/#company-users">https://github.com/tkf/emacs-jedi/#company-users</a></li>
</ol>