EMACS:in alkeet – osa 3

Emacsin alkeet – Osa 3

Olemme aiemmin oppineet, kuinka Emacs-paketit saadaan asentumaan automaattisesti ja kuinka editorin jokapäiväistä käytettävyyttä saadaan parannettua. Lisäksi edellisessä blogitekstissä asensimme Magit-paketin, jotta Emacsimme toimii myös tehokkaana Git-käyttöliittymänä.

Tällä kertaa rakennamme Emacsista monipuolisen Python-editorin. Pythonin kanssa tekemisiin joutuu nykyään lähes jokainen koodari, joten sille asetusten konfiguroiminen ei mene ainakaan hukkaan. Korjaamme myös muutamia syntaksitarkistuksessa havaittavia varoituksia ja tutustumme joukkoon uusia pieniä kikkoja, joilla editorista tehdään entistä miellyttävämpi ohjelmointityökalu.

Emacs käärmeenlumoajana

Emacsin sisäänrakennettu python.el-paketti on ominaisuuksiltaan varsin rajoittunut, joten Python-ohjelmointiin suositellaan vahvasti jotain ulkoista IDE-pakettia. Vaihtoehtoja ovat muun muassa Elpy (https://github.com/jorgenschaefer/elpy), Jedi (https://github.com/tkf/emacs-jedi) ja Anaconda mode (https://github.com/pythonic-emacs/anaconda-mode). Tässä tekstissä keskitymme Elpyyn, joka on nykyään näistä paketeista suosituin ja parhaiten tuettu. Elpy myös käyttää Jediä pintansa alla.

Elpyn voimme asentaa Emacsiimme kirjoittamalla seuraavan pätkän init.elimme loppuun:

(use-package elpy

  :ensure t

  :init (elpy-enable))

Komento elpy-enable myös alustaa Elpyn suoraan käytettäväksi Python-tiedostojen kanssa ilman ylimääräistä konfigurointia. Kun Elpy käynnistetään ensimmäistä kertaa, se pyytää asentamaan RPC-dependenssit automaattisesti. Tämä kannattaa hyväksyä, sillä Elpy toimii käynnistämällä Python-prosessin taustalla ja kommunikoimalla RPC-rajapinnan kautta.

 

Elpy pyytää aluksi lupaa asentaa RPC-dependenssit.

 

Elpyn mukana tulee useita hyödyllisiä ominaisuuksia ja komentoja, jotka alkavat etuliitteellä elpy-. Eräitä käytetyimpiä lienevät määritelmään hyppääminen ja automaattinen täydentäminen. Määritelmään hyppääminen onnistuu asettamalla kursori symbolin (esim. muuttujanimen) päälle ja painamalla näppäinyhdistelmää M-. Hyppääminen takaisin onnistuu näppäinyhdistelmällä M-,. Automaattinen täydentäminen ei vaadi erillisiä näppäinkomentoja, mutta jos se ei jokaisessa tilanteessa aktivoidu, sen voi pakottaa antamaan ehdotuksen myös näppäinyhdistelmällä C-M-i.

 

Elpy osaa automaattisesti ehdottaa täydennyksiä.

 

Elpyn konfigurointi onnistuu näppärästi paketin oman UI-ikkunan kautta, jonka voi avata Python-bufferissa komennolla M-x elpy-config.

 

Elpy Configuration -ikkunassa voi hallinnoida Elpyn asetuksia sekä sen alaisuuteen asennettuja paketteja.

 

Huomaa, että voit aktivoida olemassa olevan Python-virtuaaliympäristön M-x pyvenv-activate- tai M-x pyvenv-workon-komennoilla.

Vaihdetaan Elpyn käyttämäksi Python-versioksi Python 3. Python 3 täytyy olla tätä varten valmiiksi asennettuna tietokoneellasi. Voimme tehdä versiovaihdon Elpyn valikkojen kautta. Kun viemme kursorin boldatun Python-rivin päälle ja painamme enteriä, seuraava bufferi avautuu:

 

Voimme muokata Python-versiota value menu -valikon kautta ja sen jälkeen tallentaa valinnamme state-valikosta

 

Lopputuloksena Python-versio on vaihtunut 2:sta 3:een.

 

Asetetaan myös käytettäväksi Python-tulkiksi Python 3 vastaavasti valikoiden kautta kohdasta Interactive Python. Asetusten muokkauksen tuloksena init.elimme loppuun pitäisi ilmestyä seuraava Emacsin sisäänrakennetun Custom-järjestelmän generoima koodinpätkä:

(custom-set-variables

 ;; custom-set-variables was added by Custom.

 ;; If you edit it by hand, you could mess it up, so be careful.

 ;; Your init file should contain only one such instance.

 ;; If there is more than one, they won't work right.

 '(elpy-rpc-python-command "python3")

 '(package-selected-packages

   (quote

(flycheck elpy magit helm auto-package-update use-package)))

 '(python-shell-interpreter "python3"))

(custom-set-faces

 ;; custom-set-faces was added by Custom.

 ;; If you edit it by hand, you could mess it up, so be careful.

 ;; Your init file should contain only one such instance.

 ;; If there is more than one, they won't work right.

 )

Olennainen koodi on custom-set-variables-komento. Sen myötä aiemmin valikoiden kautta asettamamme Elpyn Python-asetukset ladataan uudelleen aina Emacsin käynnistyksen yhteydessä, kun init.el suoritetaan. Valikoista asetuksia tallentaessa Emacsin Custom-järjestelmä generoi siis init.eliin koodin, joka varmistaa, että asetukset säilyvät joka uudelleen käynnistyksellä. Näin voimme tuoda samat Emacs-asetukset myös kokonaan toiseen ympäristöön vain siirtämällä sinne init.elimme.

Jos haluamme asettaa Elpyn Python-version init.elissä manuaalisesti koodia kirjoittamalla, sekin onnistuu:

(use-package elpy

  :ensure t

  :init (elpy-enable)

  :config (setq elpy-rpc-python-command "python3"

python-shell-interpreter "python3"))

Funktio setq, jota olemme jo aiemmin käyttäneet, asettaa muuttujalle arvon. Kirjain q tarkoittaa, että ensimmäistä argumenttia edeltää heittomerkki automaattisesti, eikä sitä  tarvitse siis erikseen kirjoittaa. Funktio setq on siis yhtäläinen kuin funktio set käytettynä heittomerkin kanssa.

(setq elpy-rpc-python-command "python3")

on siis sama asia kuin

(set 'elpy-rpc-python-command "python3")

Säädetään myös paketin näppäinasetuksia. Tällä hetkellä oletusasetukset lukittavat ärsyttävästi kursorilla loikkimisen C-<up>- ja C-<down>-näppäinyhdistelmillä silloin, kun kursori on keskellä koodiblokkia. Otetaan tämä asetus pois käytöstä asettamalla näppäinyhdistelmien mappaus arvoon nil, kun Elpy on käytössä.

(use-package elpy

  :ensure t

  :bind (:map elpy-mode-map

       ("C-<up>" . nil)

       ("C-<down>" . nil))

  :init (elpy-enable))

Tämän muutoksen jälkeen loikkiminen kursorilla onnistuu kuten ennenkin. 

Elpy käyttää syntaksitarkistukseen oletuksena Emacsin sisäänrakennettua Flymake-moodia. Sitä varten tarvitsee myös asentaa erikseen Python-syntaksitarkistin, joka oletuksena on flake8. 

Kömpelön Flymaken sijaan otamme käyttöön Flycheck-syntaksitarkastuspaketin, joka tukee huomattavasti suurempaa määrää kieliä kuin Flymake ja on helpommin konfiguroitavissa. Sitä varten meidän pitää asentaa Flycheck use-packagea käyttäen, minkä lisäksi on viisasta poistaa Flymake Elpyn käyttämistä moduuleista:

(use-package elpy

  :ensure t

  :bind (:map elpy-mode-map

       ("C-<up>" . nil)

       ("C-<down>" . nil))

  :init (elpy-enable)

  :config (setq elpy-modules 

(delq 'elpy-module-flymake elpy-modules)))



(use-package flycheck

  :ensure t

  :config (global-flycheck-mode))

Yllä olemme ottaneet Flycheckin käyttöön globaalisti, joten syntaksitarkistus ajetaan nyt jokaisessa bufferissa, jonka sisällön Flycheck ymmärtää. Lisäksi olemme poistaneet elpy-module-flymaken elpy-modules-listasta.

Funktio delq poistaa ensimmäisenä argumenttina annetusta listasta kaikki objektit, jotka ovat yhtäsuuria kuin toisena argumenttina annettu objekti. Yhtäsuuruus tässä tarkoittaa, että funktio eq palauttaa objekteille arvon true. 

 

Nyt Flycheck näyttää syntaksivirheet koodissamme.

 

Jos emme halua käyttää Flycheckiä joka bufferissa vaan ainoastaan Python-buffereissa, voimme jättää myös globaalin moodin asettamatta ja käyttää sen sijaan Elpyn kanssa niin sanottua hookkia:

(use-package flycheck

  :ensure t

  :hook (elpy-mode . flycheck-mode))

Nyt hook aktivoi Flycheck-moodin ainoastaan, kun Elpy-moodi aktivoituu, eli vain Python-buffereiden yhteydessä.

Flycheck osaa yksinkertaisen syntaksitarkistuksen automaattisesti, mutta jos käytössäsi on esimerkiksi flake8, pylint tai mypy, sen pitäisi osaita havaita näiden asennus automaattisesti, kun ne on asennettu Pythonin pip-pakettimanagerin kautta. Jos Flycheckin toiminnan kanssa syntyy ongelmia, tarkistimen voi verifioida bufferikohtaisesti komennolla M-x flycheck-verify-setup tai C-c ! v.

 

Flycheck-verifioinnin antamasta tulosteesta näemme, että flake8-asennus on löytynyt, mutta konfiguraatiotiedostoa ei. Tästä huolimatta python-flake8-tarkistin on otettu käyttöön oletusasetuksilla.

 

Syntaksitarkistimien käyttöön ottaminen virtuaaliympäristössä saattaa vielä tuottaa ongelmia. Perehdymme tähän aiheeseen ja Flycheckiin yleisesti kuitenkin myöhemmissä kirjoituksissa. Näillä asetuksilla pääsemme jo hyvin alkuun.

Laitetaan vielä Emacs autoformatoimaan Python koodimme aina tallennuksen yhteydessä. Voimme formatoida koodimme myös Elpyllä käyttäen pikanäppäintä C-c C-r f tai komentoa elpy-format-code. 

Elpy käyttää joko yapf-, autopep8- tai black-muotoilijaa riippuen siitä, mitkä niistä on asennettuna ja minkä se löytää ensin. Jokaisen näistä muotoilijoista voi käynnistää myös erikseen komennoilla elpy-yapf-fix-code, elpy-autopep8-fix-code ja elpy-black-fix-code. On kuitenkin mukavinta, jos editorimme hoitaa muotoilun automaattisesti, siltä varalta, että itse unohdamme sen tehdä.

Automaattinen muotoilu hoituu seuraavalla hookilla:

 (use-package elpy

  :ensure t

  :bind (:map elpy-mode-map

       ("C-<up>" . nil)

       ("C-<down>" . nil))

  :hook (elpy-mode . (lambda ()

         (add-hook 'before-save-hook

      'elpy-format-code nil 'local)))

  :init (elpy-enable)

  :config (setq elpy-modules (

delq 'elpy-module-flymake elpy-modules)))

Hookkimme määrittelee nyt, että elpy-moden käynnistyessä ajetaan pistettä seuraava niin sanottu lambda-funktio. Lambda-funktiot ovat anonyymeja funktioita, joita voi määritellä yhdellä rivillä. Lambda-funktiomme lisää bufferille uuden paikallisen hookin, tarkemmin sanottuna before-save-hookin, jonka määräämä funktio elpy-format-code puolestaan ajetaan joka kerta ennen tallennusta. 

Kolmas add-hook-funktion argumentti, ‘local, määrittää, että tämä before-save-hook lisätään ainoastaan kullekin bufferille lokaalisti. Emme siis halua before-save-hookkimme jäävän voimaan Elpy-buffereiden ulkopuolella. Muuten elpy-format-code ajettaisiin aina, kun tallennamme minkä tahansa bufferin, esimerkiksi init.elin, ja saisimme turhia virheviestejä viestilokiimme.

Jos haluamme ajaa aina jonkin tietyn muotoilijan, vaihdamme elpy-format-coden tilalle vain haluamamme funktion, esim. elpy-yapf-fix-code.

Varoittavia esimerkkejä

Flycheckin käyttöönoton myötä huomaamme myös muutamia tarkistimen varoituksia omassa init.el-tiedostossamme. Esimerkiksi tiedoston alussa:

 

Flycheck näyttää varoitukset oranssilla alleviivattuina.

 

Hankkiudutaan nyt eroon näistä sinänsä triviaaleista varoituksista noudattamalla hieman parempia käytäntöjä.

Tiedostomme alussa ja lopussa Flycheck valittaa puuttuvista dokumentaatioriveistä. Nämä rivit on helpoin lisätä valitsemalla Emacsin ylävalikosta Emacs-Lispin alta optio Check Documentation Strings. Voit päättää kuvausten sisällön itse.

Desktop-muuttujille arvon asettaminen setq-funktiolla tuottaa puolestaan seuraavan varoituksen: assignment to a free variable.

 

Työpöytämuuttujat tuottavat läjän varoituksia.

 

Tämä varoitus johtuu siitä, että setq-funktio asettaa arvot globaalille muuttujille, joita ei ole Emacs Lisp -tavukääntäjän näkökulmasta määritelty missään. Tavukääntäjä ei nimittäin ymmärrä, että muuttuja on määritelty eri tiedostossa. 

Flycheckin syntaksitarkastus käyttää hyväksi tavukääntäjän tuottamaa tavukoodia. Kun init.el ladataan ja otetaan käyttöön Emacsissamme, tiedostoa ei kuitenkaan tavukäännetä, joten nämä varoitukset ovat meidän kannaltamme turhia. Ne voi helpoiten poistaa alustamalla nämä muuttujat defvar-funktiolla nimenomaisesti tavukääntäjää varten:

(eval-when-compile

  (defvar desktop-save)

  (defvar desktop-path)

  (defvar desktop-load-locked-desktop)

  (defvar desktop-auto-save-timeout))




(setq desktop-save t

   desktop-path '("~/.emacs.d/")

   desktop-load-locked-desktop t

   desktop-auto-save-timeout 5)

Makrolle eval-when-compile annetut komennot evaluoidaan ainoastaan tavukääntämisen yhteydessä. Koodia on myös siistitty asettamalla työpöytämuuttujamääritykset yhden setq-kutsun alle.

Alempana koodissa, Helm-konfiguraatioiden kohdalla, saamme vastaavat varoitukset helm-find-files-map- ja helm-read-file-map-muuttujille. Tämä johtuu siitä, että helm-files.el-tiedosto, jossa nämä funktiot sijaitsevat, sijaitsee kokonaan eri hakemistossa kuin helm.el, jonka use-package lataa. Tavukääntäjä luulee siis, ettei näitä funktioita ole määritelty. Voimme jälleen informoida tavukääntäjää ja siirtää nämä defvar-määritykset koodimme alkuun:

;; inform byte compiler about free variables

(eval-when-compile

  (defvar desktop-save)

  (defvar desktop-path)

  (defvar desktop-load-locked-desktop)

  (defvar desktop-auto-save-timeout)

  (defvar helm-find-files-map)

  (defvar helm-read-file-map))

Viimeiset vinkit

Tutustutaan vielä lopuksi muutamaan kikkaan, joilla Emacsista saadaan entistä käytettävämpi. Ensimmäiseksi asetetaan Emacs poistamaan tiedostojemme rivien lopusta ylimääräiset välilyönnit ja muut whitespace-merkit, joita syntyy helposti koodatessa. Näin pidämme myös muut koodarit tyytyväisenä, kun jokainen uusi committimme ei tuota läjää uusia turhia välilyöntejä. Temppu hoituu suoraviivaisesti seuraavalla rivillä, jonka voi lisätä esimerkiksi miscellaneous settings -kohdan alle:

(add-hook 'before-save-hook 'delete-trailing-whitespace)

Toinen arkemme mukavoitus on laittaa Emacs lataamaan aina uusin versio tiedostosta bufferiin, jos tiedoston sisältö vaihtuu levyllä. Tästä on erityisesti hyötyä esimerkiksi Gitin kanssa, kun vedämme etärepositoriostamme uusimman version koodista koneellemme. Asetuksen konfigurointi onnistuu laittamalla buffereiden automaattinen palautus globaalisti päälle:

(global-auto-revert-mode t)

Mukavaa on myös, jos Emacs ilmoittaa meille rivinumeroiden lisäksi sarakkeen numeron jokaisessa bufferissa. Tämä onnistuu seuraavasti:

(setq column-number-mode t)

Tarkkaavaisimmat ovat huomanneet jo, että Emacsimme sisentää init.elissä sarkainmerkkejä käyttäen. Koska välilyönnit ovat itsestään selvästi parempi vaihtoehto, saamme kyseisen kammotusasetuksen pois käytöstä asettamalla indent-tabs-moden arvoon nil:

(setq-default indent-tabs-mode nil)

Tässä tapauksessa käytämme funktiota setq-default, joka asettaa arvon vain niille buffereille, joilla ei ole jo omaa arvoa. Siten jos myöhemmin koodaamme esimerkiksi Golangilla, jossa sarkaimet kuuluvat kielen vaadittuihin käytäntöihin, Emacsin Golang-työkalumme osaavat edelleen käyttää sarkaimia välilyöntien sijaan.

Kun olemme tallentaneet asetuksen ja käynnistäneet Emacsin uudestaan, voimme pyyhkiä init.elistämme turhat sarkaimet pois. Valitse koko bufferi näppäinyhdistelmällä C-h ja komennolla M-x replace-string korvaa sarkaimet välilyönneillä. Sen jälkeen voit vielä varmistaa yhdistelmällä C-h TAB, että sisennykset menevät oikein.

Lopuksi voimme vielä vaihtaa Emacsiin mieleisemme teeman. Custom Themes -bufferin saat auki komennolla M-x customize-themes. Sieltä voit testailla eri teemoja ja tallentaa mieleisesi.

 

Emacsissa tulee joukko teemoja valmiiksi asennettuna ja lisää löytää Internetistä.

 

Kun tallennat teeman, custom-set-variables-komento init.elin lopussa päivittyy seuraavasti:

(custom-set-variables

 ;; custom-set-variables was added by Custom.

 ;; If you edit it by hand, you could mess it up, so be careful.

 ;; Your init file should contain only one such instance.

 ;; If there is more than one, they won't work right.

 '(custom-enabled-themes (quote (misterioso)))

 '(elpy-rpc-python-command "python3")

 '(python-shell-interpreter "python3"))

(custom-set-faces

 ;; custom-set-faces was added by Custom.

 ;; If you edit it by hand, you could mess it up, so be careful.

 ;; Your init file should contain only one such instance.

 ;; If there is more than one, they won't work right.

 )

Teeman voi asettaa myös manuaalisesti komennolla

(load-theme 'misterioso)

Lopulta init.elimme pitäisi näyttää jotakuinkin seuraavanlaiselta:

;;; init.el --- Initialization file for Emacs


;;; Commentary:

;; Emacs Startup File


(require 'package)

;;; Code:


(add-to-list

 'package-archives

 '("melpa" . "http://melpa.org/packages/"))


(unless package--initialized (package-initialize))



;; inform byte compiler about free variables

(eval-when-compile

  (defvar desktop-save)

  (defvar desktop-path)

  (defvar desktop-load-locked-desktop)

  (defvar desktop-auto-save-timeout)

  (defvar helm-find-files-map)

  (defvar helm-read-file-map))


;; desktop-save settings

(desktop-save-mode 1)


(setq desktop-save t

   desktop-path '("~/.emacs.d/")

   desktop-load-locked-desktop t

   desktop-auto-save-timeout 5)


;; miscellaneous settings

(add-to-list 'default-frame-alist '(fullscreen . maximized))

(add-hook 'before-save-hook 'delete-trailing-whitespace)

(global-auto-revert-mode t)

(setq column-number-mode t)

(setq-default indent-tabs-mode nil)


;; automatic package installation

(unless (package-installed-p 'use-package)

  (package-refresh-contents)

  (package-install 'use-package))


;; auto updates

(use-package auto-package-update

  :ensure t

  :config (setq auto-package-update-delete-old-versions t

             auto-package-update-interval 4)

  (auto-package-update-maybe))


;; external package configurations

(use-package helm

  :ensure t

  :bind (("M-x" . helm-M-x)

      ("C-x C-f" . helm-find-files)

      ("C-x b" . helm-mini)

      :map helm-map

      ("<tab>" . helm-execute-persistent-action)

      ("C-z" . helm-select-action)

      :map helm-find-files-map

      ("<DEL>" . helm-ff-delete-char-backward)

      ("C-<backspace>" . helm-find-files-up-one-level)

      :map helm-read-file-map

      ("<DEL>" . helm-ff-delete-char-backward)

      ("C-<backspace>" . helm-find-files-up-one-level))

  :init (helm-mode 1))


(use-package magit

  :ensure t

  :bind (("C-x g" . magit-status)))


(use-package elpy

  :ensure t

  :bind (:map elpy-mode-map

           ("C-<up>" . nil)

           ("C-<down>" . nil))

  :hook (elpy-mode . (lambda ()

                    (add-hook 'before-save-hook

                              'elpy-format-code nil 'local)))

  :init (elpy-enable)

  :config (setq elpy-modules (delq 'elpy-module-flymake elpy-modules)))


(use-package flycheck

  :ensure t

  :config (global-flycheck-mode))


(custom-set-variables

 ;; custom-set-variables was added by Custom.

 ;; If you edit it by hand, you could mess it up, so be careful.

 ;; Your init file should contain only one such instance.

 ;; If there is more than one, they won't work right.

 '(custom-enabled-themes (quote (misterioso)))

 '(elpy-rpc-python-command "python3")

 '(python-shell-interpreter "python3"))

(custom-set-faces

 ;; custom-set-faces was added by Custom.

 ;; If you edit it by hand, you could mess it up, so be careful.

 ;; Your init file should contain only one such instance.

 ;; If there is more than one, they won't work right.

 )


(provide 'init)


;;; init.el ends here


Blogin seuraavassa osassa perehdymme siihen, miten Emacsiin saadaan näppärästi upotettua myös terminaali. Näin meidän tarvitsee hyppiä entistä vähemmän eri ohjelmaikkunoiden välillä!

 

One thought on “EMACS:in alkeet – osa 3

  1. JP says:

    Moi! Nämä Emacs kirjoitukset ovat aivan loistavia. Toivottavasti niitä tulee lisää. 🙂

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *