Für die Entwicklung in Rust verwende ich Emacs und setze dafür die Modi rustic1 und lsp ein. Weiterhin sind die Language-Server rls oder rust-analyzer (externe Programme) notwendig, damit Emacs sich Informationen über den Code und die Syntax holen kann. Language-Server gibt es für verschiedene Sprachen (bis hin zu JSON und HTML/CSS), damit nicht jeder Editor (und jeder Major-Mode) das Analysieren der Programmiersprachen implementieren muss.
-
Der Major-Mode rustic ist ein Abkömmling von rust-mode, der aber tendenziell wieder in rust-mode einfließen soll. ↩
Der Modus rustic bietet solch grundlegende Funktionen wie Einfärbung oder Einrückung des Codes, die bei der Programmierung helfen. Aber darüber hinaus bietet das Duo rustic/lsp noch viele Hilfen wie Vervollständigung, Zugriff auf die Dokumentation von Funktionen oder Navigation im Code. Neben Emacs habe ich aber immer noch mehrere XTerms geöffnet, um mit cargo den Code zu übersetzen und auszuführen.
Auf der Webseite von lsp gibt es:
- eine Galerie mit Bildern, wie lsp aussieht,
- einige Anleitungen zur Einrichtung vor allem für andere Sprachen und mit noch mehr Bildern
- die List der wichtigsten Variablen für Einstellungen
- eine Übersicht der Tastenkürzel.
Gute Unterstützung zu lsp bekommt man im Chat (auf Englisch, für Deutsch schreibt mir einfach):
- Matrix #emacs-lsp_lsp-mode:gitter.im
- Gitter emacs-lsp/lsp-mode
- Discord
Rustic und lsp
Language-Server installieren
Der Minor-Mode lsp ist ein allgemeiner Modus für verschiedene Sprachen, die
einen extern Language-Server unterstützen. Damit muss nicht mehr im Emacs die
Auswertung des Quelltexts implementiert sein, was mit den Jahren immer
umfangreicher und komplizierter geworden ist. Language-Server können mit
M-x lsp-install-server
installiert werden, aber dies habe ich noch nicht
ausprobiert. Ich habe den Language-Server direkt mit rustup installiert.
Für Rust gibt es gegenwärtig (Jan. 2021) zwei Language-Server: rls und die Weiterentwicklung rust-analyzer, wobei rust-analyzer wieder in rls 2.0 einfließen soll. Ich habe beide Language-Server installiert und nutze vorwiegend rust-analyzer, weil lsp-mode diesen bevorzugt auswählt. Zusätzlich benötigt man noch die Komponenten rust-src für die Beschreibungen der Funktionen der Standardbibliothek und rust-analysis1.
-
Bei der Komponente bin ich mir nicht sicher, ob man die wirklich braucht ↩
% rustup component add rls rust-analysis rust-analyzer-preview rust-src
Für Cargo gibt es auch ein Paket ra_ap_rust-analyzer, aber mit diesem Programm kommt es bei mir gerade (2020-12-09, v0.0.27) zu Fehlern. Ich hoffe, dieses Wirrwarr löst sich irgendwann auf und man muss nur rls installieren.
Zu rls ist mit zu erwähnen, dass dieses oft nicht aktualisiert werden kann, weil die Entwicklung kontinuierlicher Anpassungen bedarf. Deshalb sieht man oft folgende Meldung. Daran also nicht stören, nach einigen Tagen geht es wieder.
% rustup update
info: syncing channel updates for 'nightly-x86_64-unknown-linux-gnu'
info: latest update on 2021-01-08, rust version 1.51.0-nightly (c8915eebe 2021-01-07)
info: skipping nightly which is missing installed component 'rls'
Rustic und lsp für Emacs einrichten
Im Emacs kann man mit M-x package-install
die Pakete rustic und lsp-mode
installieren. Zusätzlich ist das Paket flycheck1 nützlich,
um während des Programmierens Fehler angezeigt zu bekommen.
-
Die Konfiguration wird von lsp-mode automatisch vorgenommen, sobald das Paket installiert ist. ↩
Falls der Pfad zu den Cargo-Programmen nicht in PATH enthalten ist, kann man diesen in der init.el hinzufügen. Zusätzlich muss für Emacs noch die interne Liste exec-path angepasst werden:
(add-to-list 'exec-path (concat (getenv "HOME") "/.cargo/bin"))
(setenv "PATH" (concat (getenv "HOME") "/.cargo/bin:" (getenv "PATH")))
Da bei mir auch der Pfad zur Rust-Toolchain nicht in der allgemeinen Pfadliste steht, muss ich den Pfad zu rust-analyzer noch bekannt geben:
(with-eval-after-load 'rustic
(setq rustic-analyzer-command
'("/home/joerg/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rust-analyzer"))
)
Verzögerter Start mit Desktop-save-mode
Wenn man sich mit desktop-save-mode beim Start von Emacs den letzten Zustand wiederherstellen lässt, kann mit lsp-mode der Start zu einer Compilier-Orgie führen. Daher ist es besser, die Initialisierung von lsp-mode auf das erste Öffnen des Puffers zu verlagern. Leider wird lsp-mode direkt von rustic geladen, weshalb man dieses zuerst unterbinden und dann händisch die verzögerte Initialisierung von lsp-mode starten muss:
; use lsp-deferred (in rustic-mode-hook) instead of lsp-mode
(setq rustic-lsp-client nil)
(add-hook 'rustic-mode-hook
(lambda ()
(rustic-lsp-mode-setup)
; defer start of lsp until the buffer is visited, prevent high load
; on Emacs startup due to desktop-save-mode
(lsp-deferred)
))
flycheck nicht automatisch aktivieren
Diverse Untermodi bis hin zu desktop-save-mode aktivieren immer wieder flycheck, was meist einen Kompiliervorgang anstößt oder Fehler produziert. Daher habe ich diverse Stellen ausgemacht, an denen ich flycheck aus dem Automatismus herausgenommen habe und nur vom lsp-diagnostics-mode starten lasse; ggf. passiert dies verzögert, was sich positiv auf den Start von Emacs auswirkt.
(add-to-list 'desktop-minor-mode-handlers '(flycheck-mode . ignore))
(with-eval-after-load 'rustic-flycheck
; lsp-mode starts flycheck (if file is writable)
(remove-hook 'rustic-mode-hook 'flycheck-mode)
)
;; Don't start flycheck automaticly, but do so by the lsp-diagnostics-mode-hook
;; only for writable files
(with-eval-after-load 'lsp-diagnostics
(setq lsp-diagnostics-provider :none)
(add-hook 'lsp-diagnostics-mode-hook
(lambda ()
(unless buffer-read-only (lsp-diagnostics-flycheck-enable))
))
)
Da sich schreibgeschützte Dateien eh nicht korrigieren lassen, bringt es auch nichts, wenn flycheck für diese Dateien Fehler meldet. Das Kompilieren der Systembibliotheken ist außerdem zu aufwändig, sollte man mal mit der Symbolverfolgung dorthin gelangen.
Systembibliotheken schreibgeschützt öffnen
Da ich mit der Navigation auch gern in die Quellen der Systembibliotheken springe, diese aber nicht verändern kann, sollen diese Puffer einen Schreibschutz erhalten.
(add-hook 'find-file-hook
(lambda ()
(when (string-match-p "/.\\(cargo\\|cache\\)/.*\\.rs\\'" buffer-file-name)
(read-only-mode t)
)
))
Integration von which-key
Mit dem Paket which-key bekommt man nach einer kurzen Verzögerung zum Anfang einer Tastenfolge die möglichen Fortsetzungen angezeigt.1 Für lsp-mode gibt es dafür noch zusätzliche Beschreibungen, die bei der Aktivierung initialisiert werden müssen:
-
which-key muss in init.el global mit
(which-key-mode t)
aktiviert werden. ↩
(add-hook 'lsp-mode-hook #'lsp-enable-which-key-integration)
lsp-ui für Hinweisfenster
Das Paket lsp-ui zeigt nützliche Hinweise, wie die Dokumentation, weitere Verwendungsstellen in der Umgebung oder Verbesserungsvorschläge, in zusätzlichen Fenstern an. Wenn man mit dem Cursor oder der Maus auf einen Funktions- oder Variablennamen geht, zeigt es den Typ und andere Informationen dazu an.
Mir ist das allerdings zu unruhig und daher habe ich die Hilfe deaktiviert und mir stattdessen eine Funktion zum manuellen Ein- und Ausschalten des Hilfefensters gebaut. Die Verzögerung bis die Zusatzinformationen angezeigt werden, habe ich auch auf 1 Sekunde gesetzt, um weniger Ablenkung zu haben. Wie man noch mehr deaktivieren kann, ist in einer Anleitung beschrieben.
(setq lsp-ui-doc-delay 1)
(defun lsp-ui-doc-toggle (arg)
"Show/hide documentation of the symbol at point
If ARG is unset, toggle the state. With ARG 0, hide the documentation,
else show it."
(interactive "P")
(if (if (null arg) (null (lsp-ui-doc--frame-visible-p)) arg)
(lsp-ui-doc-show)
(lsp-ui-doc-hide))
)
(with-eval-after-load 'lsp-ui-doc
; disable doc popup, but toggle it by keys
(setq lsp-ui-doc-show-with-cursor nil)
(define-key lsp-ui-doc-mode-map "\C-cd" #'lsp-ui-doc-toggle)
(define-key lsp-ui-doc-mode-map "\C-c\C-d" #'lsp-ui-doc-toggle)
)
Für die xref-Befehle zur Navigation durch den Quelltext gibt es auch noch
Ersetzungen, die reichhaltiger sind. Mit M-.
bekommt man die Definitions- und
Deklarationsstellen angezeigt, wenn es genau eine Stelle gibt, sonst springt man
sofort dorthin; es gibt auch C-x 4 .
und C-x 5 .
für eine neue Anzeige bzw.
ein neues Fenster. Zurück kommt man mit M-,
. Mit M-?
werden alle Stellen
angezeigt, wo das aktuelle Symbol verwendet wird.
(with-eval-after-load 'lsp-ui
(define-key lsp-ui-mode-map [remap xref-find-definitions] #'lsp-ui-peek-find-definitions)
(define-key lsp-ui-mode-map [remap xref-find-references] #'lsp-ui-peek-find-references)
)
Zusätzlich habe ich das Fenster noch im Aussehen angepasst, da es mir zu groß war.
(setq
lsp-ui-doc-max-width 60
lsp-ui-doc-max-height 18
)
(with-eval-after-load 'lsp-ui-doc
…
(add-hook 'lsp-ui-doc-frame-hook
(lambda (frame window)
(set-face-attribute 'default frame :height 110)
))
)
Folding mit Origami
Für den Überblick ist es oft hilfreich, einige Codestellen ausblenden zu können
– engl. folding. Mit dem Paket lsp-origami wird dies auch unterstützt. Es gibt
dann viele Befehle (M-x
) zum Ein- und Ausblenden diverser Teile, die mit
origami anfangen, aber am meisten verwende ich nur origami-close-all-nodes
um alle möglichen Stellen auszublenden und origami-recursively-toggle-node
um
Code an der Cursor-Stelle ein- bzw. auszublenden.
Da ich das Ein-/Ausblenden so häufig nutze, habe ich es an die Taste Shift+Tab
gebunden:
(with-eval-after-load 'lsp-mode
; folding
(add-hook 'lsp-after-open-hook #'lsp-origami-try-enable)
(define-key lsp-mode-map (kbd "<backtab>") #'origami-recursively-toggle-node)
…
)
Language-Server aus der Modeline entfernen
Mich stört in der Modeline der Name des Language-Servers und mir genügt völlig die Kennung LSP. Mit dem Paket diminish kann man den Eintrag entsprechend einkürzen:
(with-eval-after-load 'lsp-mode
; remove the lsp-server from the minor mode indicator in the mode line and
; only show "LSP"
(diminish 'lsp-mode "LSP")
…
)
Diverse Anzeigen abschalten
Mit all den Funktionen, die lsp-mode bietet, wird jede Cursorbewegung zum Leuchtfeuerwerk, weil ständig irgendwelche Fehlermeldungen und Vorschläge angezeigt werden. Mich nervt das und ich will meine Ruhe beim Programmieren. Deshalb habe ich mithilfe der Anleitung zum Deaktivieren einiger Funktionen mir die Code-Aktionen am rechten Rand und Funktionsdeklarationen in der Fußzeile deaktiviert.
(setq
lsp-ui-sideline-show-code-actions nil
lsp-signature-render-documentation nil
)
Definitions- und Nutzungsstellen finden
Die Stelle, an der eine Funktion oder Variable definiert ist, kann in vielen
Programmiermodi mit M-.
(xref-find-definitions
) angesprungen werden. Zurück
kommt man mit M-,
(xref-pop-marker-stack
). Die Stellen, an der eine Funktion
oder Variable genutzt wird, können mit M-?
(xref-find-references
) angezeigt
werden.
Dies Funtioniert auch mit lsp und lsp bietet auch spezielle Funktionen, die die Definitions- und Nutzungsstellen genauer anzeigen. Hierfür kann man die oben erwähnten Tastenkombinationen in der init.el umdefinieren:
(with-eval-after-load 'lsp-ui
(define-key lsp-ui-mode-map [remap xref-find-definitions] #'lsp-ui-peek-find-definitions)
(define-key lsp-ui-mode-map [remap xref-find-references] #'lsp-ui-peek-find-references)
)
Hervorhebung von Nutzungsstellen
Hilfreich bei der Programmierung ist auch die Hervorhebung aller Stellen auf dem Bildschirm, an denen eine Variable oder Funktion genutzt wird. Dazu muss man nur kurz an einer Stelle verweilen und die anderen Stellen leuchten auf.
Variablen umbenennen
Sehr praktisch zum Überarbeiten des Codes ist die Umbenennung von Variablen,
Funktionen oder Datentypen inklusive aller Nutzungsstellen mit lsp-rename
.
Dafür einfach den Cursor auf dem Namen platzieren und M-x lsp-rename
aufrufen.
Die Änderungen erfolgen auch dateiübergreifend.
Expansion von Makros
Eine Stärke von Rust sind die Makros. Jedoch ist bei mancher Compilermeldung zu
einem Fehler nicht klar erkenntlich, was das Problem ist. Mit dem Paket
cargo-expand kann man sich auf der Kommandozeile den kompletten Quelltext
eines Programme, dessen Makros ausgelöst wurden, ansehen. Innerhalb von Emacs
kann man aber auch mit dem Cursor über einem Makro einfach
M-x lsp-rust-analyzer-expand-macro
aufrufen und bekommt das Ergebnis dieses
eines Makros angezeigt.
Leider ist im Rustic gegenwärtig (Jan. 2021) ein Fehler, weshalb man in der init.el folgende Anpassung treffen muss:
(with-eval-after-load 'rustic
; https://github.com/brotzeit/rustic/issues/211
(setq lsp-rust-analyzer-macro-expansion-method 'lsp-rust-analyzer-macro-expansion-default)
)
Vervollständigungen
Grundsätzlich kann ich für Vervollständigungen das Emacs-Paket yasnippet
empfehlen. Ich habe mir damit auch für Rust einige nette Helferlein gebastelt
bis hin zu großen Codeschnipseln mit den Anweisungen für clap. Mit der
Funktion yas-new-snippet
geht dies sehr einfach:
p ⇒ println!("${1:{}}", $0);
s ⇒ Some($1)$0
ok ⇒ Ok(${1:()})$0
Da Rust mit Typen arbeitet, ist fast überall immer bekannt, welche Methoden ein Objekt besitzt. Hierfür habe ich das Emacs-Paket company installiert, womit sich nach einer kurzen Verzögerung ein kleines Fenster unter dem Cursor öffnet und die möglichen Vervollständigungen anzeigt.
Magische Vervollständigungen
Rust-analyzer bietet einige besondere
Vervollständigungen
für Ausdrücke, die keine echten Funktionen sind, sondern den Ausdruck umwandeln.
Praktisch ist zum Beispiel an einen Wert .dbg
anzuhängen und mit Tab
die
Umwandlung auszulösen. Dies führt zu dbg!(…)
.
Mit expr.if
erhält man if expr {}
oder if let … {}
, wenn der Ausdruck ein
Option
oder Result
ist. Im zweiten Fall kann man auch mit .match
sich die
entsprechende Anweisung match … { … => }
generieren lassen.
Code-Aktionen
Neben den Vervollständigungen kann man auch an bestimmten Punkten Aktionen wie
das Einfügen von Vorlagen für die Implementation von Traits, das Einfügen von
use-Anweisungen bei bekannten Typen, das Umwandeln zwischen if let
und
match
oder Code/Anweisungen in Module/Funktionen/Variablen überführen. Man
muss nur an der passenden Stelle die Funktion lsp-execute-code-action
aufrufen; wer das Emacs-Paket helm nutzt, kann auch helm-lsp-code-actions
verwenden. Wo sich diese Stellen befinden, ist in der
Dokumentation
sehr gut dargestellt. Ob an einer Stelle eine Aktion verfügbar ist, sieht man an
der Glühbirne am Ende der
Modeline.
Wer in der Konfiguration nicht lsp-ui-sideline-show-code-actions
abgeschaltet
hat, bekommt die Möglichkeiten immer am rechten Rand angezeigt. Aber mir ist die
Anzeige damit zu unruhig. Die Glühbirne genügt mir.
Cargo-Befehle aufrufen
Mit restic kommen auch eine Reihe an Funktionen für den Aufruf von
cargo-Befehlen. Nützlich ist zum Beispiel rustic-cargo-add
, um crates zum
Projekt hinzuzufügen, oder rustic-cargo-outdated
, um nach neueren Versionen zu
suchen. (Das Emacs-Paket cargo ist dafür nicht notwendig.)
Spielwiese zum Experimentieren
Wer play.rust-lang.org kennt, wird das Paket
rust-playground lieben. Damit lässt sich sehr leicht ein neues Projekt
erstellen, mit C-c C-c
kompilieren und mit C-c k
wieder löschen. Ideal, um
mal einen Codeschnippsel auszuprobieren oder zu testen, was der Rückgabewert
einer Funktion bei einem bestimmten Wert ist.
Aktuell (Jan. 2021) ist leider in dem Projekt ein
Fehler, der sich aber
leicht beheben lässt.
Danach das Compilieren der veränderten Datei mit M-x byte-compile-file
nicht
vergessen, sonst wird weiterhin die Original-elc-Datei verwendet.
Rust-mode und Racer
Anfangs habe ich rust-mode und racer genutzt, weil ich rls, den Lanugage-Server für Rust, nicht zum Laufen bekommen hatte. Mittlerweile klappt es und ich bin umgestiegen, weil lsp mehr Funktionen wie das Umbenennen von Bezeichnern bietet. Durch die Installation beider Modi kam es bei mir anfangs zu unschönen Interferenzen, die aber durch den Wechsel bedingt waren. Wer nur einen Modus installiert hat, sollte keine Probleme haben bzw. sollte der Bugfix irgendwann in lsp einfließen.
Die folgende Anleitung ist auch etwas veraltet (Stand: Jan. 2021), sollte aber dennoch funktionieren. Ich nutze nur noch rustic und rust-analyzer.
Zum regulären Major-Mode rust gibt es den Minor-Mode racer, der die
Definition von Funktionen angezeigt, wenn man mit dem Cursor auf einem
Funktionsnamen verweilt, oder mit dem man zur Definition einer Funktion mit
M-.
springen; mit M-,
kann man zurück springen. Mit racer-describe kann
man sich die Hilfe zu einer Funktion anzeigen lassen; dies wird unten an C-c d
bzw. C-c C-d
gebunden. Durch die Einbindung des Minor-Modes company bekommt
man sogar Tab-Vervollständigung.
Racer und Rust-Quellen installieren
% rustup component add rust-src --toolchain nightly % cargo install racer
emacs-racer über das Emacs-Paketsystem installieren:
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/")) (package-install "racer")
oder direkt die Quellen:
% git clone https://github.com/racer-rust/emacs-racer.git ~/git/emacs-racer
Abhängigkeiten von emacs-racer installieren (unter Debian unstable)
% apt install elpa-f elpa-company
emacs-racer in ~/.emacs.d/init.el einrichten
(autoload 'racer-mode "~/git/emacs-racer/racer.el" "Minor mode for racer." t) (add-hook 'rust-mode-hook #'racer-mode) (add-hook 'racer-mode-hook #'eldoc-mode) (add-hook 'racer-mode-hook #'company-mode) (setq company-tooltip-align-annotations t) (with-eval-after-load 'rust-mode (define-key rust-mode-map (kbd "TAB") #'company-complete-common-or-cycle) (define-key rust-mode-map "\C-cd" #'racer-describe) (define-key rust-mode-map "\C-c\C-d" #'racer-describe) )
Siehe auch: Installationsanleitung von emacs-racer; vor yasnippet v0.13 ist Yasnippet und Company gemeinsam -nutzen notwendig.
Rust-Mode mit Rustic deaktivieren
Wenn beide Modi installiert sind, kann es passieren, dass rust-mode statt rustic für einige Dateien geladen wird. Da sich rust-mode selbst in die Liste für die Dateierweiterungen einträgt, muss man dies nach dem Laden rückgängig machen:
(with-eval-after-load 'rust-mode
; remove rust-mode from the list of file extension mappings
(setq auto-mode-alist (rassq-delete-all 'rust-mode auto-mode-alist))
)
Da auch andere Modi (bis hin zu markdown-mode) rust-mode aktivieren, habe ich zeitweise auch gezielt einen Fehler herbeigeführt, um all die Stellen zu finden, die auf rust-mode verweisen. Allerdings ist diese mit Vorsicht zu genießen, da die Fehler an allen möglichen Stellen (auf beim Start) auftreten können:
(add-hook 'rust-mode-hook
(lambda () (error "Don't use rust-mode, use rustic")
Fehlerkennzeichnung mit flyCheck
Mit flyCheck kann man sich direkt im
Quelltext die Fehler hervorheben, mit C-c ! l
die Fehler anzeigen und mit C-c
! p
bzw. C-c ! n
rückwärts oder vorwärts springen. Mehr Informationen gibt es
in der Kurzanleitung.
Unter Debian müssen die Pakete elpa-flycheck und flycheck-doc installiert
werden und über die Emacs-Paketverwaltung die Anpassung an Rust
(M-x package-install flycheck-rust
).
Anpassung in init.el:
(add-hook 'rust-mode-hook #'flycheck-mode)
(add-hook 'rust-mode-hook #'flycheck-rust-setup)
Modus für cargo
Es gibt einen Minor-Mode, um die Befehle von cargo aus Emacs heraus aufrufen
zu können. Installation mit M-x package-install cargo
und in init.el mit
(add-hook 'rust-mode-hook #'cargo-minor-mode)
aktivieren.
Weiteres
- Eine ausführliche Anleitung in Englisch: »Configuring Emacs for Rust development«