Another neat finding in Go’s language server. Basically, I wanted to
include some sort of way to run some static analyser with my language
server. I remember golanci-lint
was long the “de facto” tool for
this, but seems that staticcheck has grown a
lot in popularity. So I wanted to integrate that with my gopls
.
Naturally, the first step was installing the tool itself. Fortunately,
that can be done super easily with just:
$ go install honnef.co/go/tools/cmd/staticcheck@latest
Next I needed to enable this somehow with gopls
. Again fortunately,
all the possible settings for gopls
can be found
here.
Including the simple variable for staticcheck
. To pass in this
setting to eglot
, I need to configure variable
eglot-workspace-configuration
, which basically allows you to
configure LSP servers specifically for a given project and given LSP.
To pass in the setting to the gopls
, we need to pass in a plist
with the configurations we want:
(use-package eglot
:custom
(eglot-workspace-configuration '((:gopls . ((staticcheck . t))))))
;; OR
(setq-default eglot-workspace-configuration '((:gopls . ((staticcheck . t)))))
Naturally, you can pass any setting you desire that is available for
the language server this way.
I needed to write some funky Go code using relatively new Go
generics in it, just to quickly notice that my LSP client in Emacs
didn’t recognise those like you would expect. Fixing gopls
was
relatively straight-forward, since my issue seemed to be just an old
version. So I just installed gopls
while having Go 1.18+ installed. In
my case, I had already generics-aware Go version installed but seemed
like that I had installed my gopls
version before the generics
update, so I had to just reinstall it with:
$ go install golang.org/x/tools/gopls@latest
After that gopls
was pleased, or at least my eglot
didn’t complain
about syntactical errors in my code. Pretty much immediately I
realised that now my goimports
was broken, so it didn’t organise my
imports accordingly. I knew that gopls
was able to do this stuff
instead of using goimports
, but I just never was eager to fix
something that was already working. But since now it was broken, I
decided to find a way to fix it.
What I did was just uninstall goimports
from my machine and started
relying on gopls
for organising my imports. Previously, I had set my
Emacs so that when I saved my files, it always ran goimports
(when
working on Go files, of course). Setting my eglot
to do that was
relatively simple, but then I noticed that it only formats my code, it
doesn’t automatically import the libraries like how goimports
does.
Like I mentioned earlier, gopls
should be able to work exactly like
goimports
in this case, so I had to start digging on how I can make
my eglot
to do this. Basically, how gopls
does this, is it uses
source.organizeImports
action for it. So I needed to run that
somehow on save.
Fortunately, eglot
exports all these code actions that the LSP can
do with a neat function called eglot-code-actions
. After some
tinkering, I was able to call that before the save:
(use-package go-mode
:ensure t
:preface
(defun tok/gofmt-before-save ()
(interactive)
(gofmt-before-save)
;; Run `eglot-code-actions' only in buffers where `eglot' is active.
(when (functionp 'eglot-code-actions)
(eglot-code-actions nil nil "source.organizeImports" t))
:hook (go-mode . (lambda ()
;; Using depth -10 will put this before eglot's
;; willSave notification so that the notification
;; reports the actual contents that will be
;; saved.
(add-hook 'before-save-hook 'tok/gofmt-before-save -10 t))))
I decided to use gofmt-before-save
, which comes from go-mode
here since I noticed that if you would just run
eglot-format-buffer
formatting doesn’t open a new buffer where it
lists all the errors and instead prints them in the LSPs messages.
You can probably dig them somehow from there and print in a new
buffer, but I liked already the existing behaviour of running
gofmt
with go-mode
so I decided to use that one.
Small fix, but a really good one. Happy generics-aware hacking.
So in recent days, I have stumbled upon some REALLY NICE (at least in
my own standards) Emacs tweaks, which I wanted to share with you.
First, something very trivial, I knew that Emacs had some sort of jump
to previous location etc., type of feature available, but I never got
into using it. Turns out there’s is a built-in keybinding for that or
a couple. First, one was C-x C-x
, which jumps to the last position
and selects the text from your current position. So, e.g. you jump to
the beginning of a file and press that combination, it selects the
text from the beginning to the last position. Which was cool for me,
but I rarely need something like that.
That was enough for me for some time, but I wanted to tweak it
slightly. I didn’t care about selection and wanted to centre the
screen after the jump. Thankfully, this is relatively trivial to
implement in Emacs with a single function:
(defun jump-to-mark-and-center (arg)
(interactive "*p")
(goto-char (mark))
(recenter))
(global-set-key (kbd "C-x C-x") 'jump-to-mark-and-center)
So I bound the old keybinding to that new function, which does exactly
what I want. Lovely!
To the second lovely new configuration! I had longed for a feature in
Emacs, where I could find a file based on the format of
FILENAME:LINENUMBER
. So, if I would have a file, let’s say file.cc
and I would immediately jump to the line number 14 in that file, I
would want to open that file with file.cc:14
like most of the Unix
tools print these file locations, but I just couldn’t do it in Emacs.
Thankfully, after some searching throughout the interwebs, I found
some nice defadvice
that fixes this for me:
(defadvice find-file (around find-file-line-number
(filename &optional wildcards)
activate)
(save-match-data
(let* ((matched (string-match "^\\(.*\\):\\([0-9]+\\):?$" filename))
(line-number (and matched
(match-string 2 filename)
(string-to-number (match-string 2 filename))))
(filename (if matched (match-string 1 filename) filename)))
ad-do-it
(when line-number
;; goto-line is for interactive use
(goto-char (point-min))
(forward-line (1- line-number))))))
And BAM! It works just like that!
And the last lovely new feature! I use mainly vterm
inside my Emacs
for my terminal needs. I always wanted to use it so that when I’m
inside a certain directory in the terminal, I could just open some
file in that directory, but unfortunately, by default, vterm
only
knows the directory where it was opened at.
Thankfully, after reading some documentation about vterm
, it turns
out you’re able to send certain character codes to emacs from your
vterm
session. So you’re able to make it so that when you open
vterm
in directory x
and proceed to change the directory inside
the vterm
with many different cd
commands etc. to something like
x/many/different/subdirs
, when I run something like C-x C-f
in
that vterm
buffer, the minibuffer inside Emacs, would know that I
want to file directory in the directory where vterm
currently is,
instead of the directory where it was initially opened.
This can be done by doing some shell tweaking. I use zsh
myself, if
you use something else, refer to vterm
README.
# Enable the shell to send information to vterm via properly escaped
# sequences.
vterm_printf() {
printf "\e]%s\e\\" "$1"
}
vterm_prompt_end() {
vterm_printf "51;A$(whoami)@$(hostname):$(pwd)"
}
# Let vterm know what dir I'm in
setopt PROMPT_SUBST
PROMPT=$PROMPT'%{$(vterm_prompt_end)%}'
If you happen to use screen
or tmux
, you might need to do some
other tweaks in there, but these are mentioned in the vterm
README.
In any case, when you define those to your .zshrc
, vterm
sends the
information of the current directory straight to Emacs, so it knows
where you’re currently at. Which is great!
To make that even better, I often noticed that I wanted to open files
straight from the command line instead of running some Emacs command
to open the files. Fortunately, vterm
covers this also:
vterm_cmd() {
local vterm_elisp
vterm_elisp=""
while [ $# -gt 0 ]; do
vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")"
shift
done
vterm_printf "51;E$vterm_elisp"
}
find_file() {
vterm_cmd find-file "$(realpath "${@:-.}")"
}
alias e="find_file"
With these functions inside your .zshrc
, I can run find_file
inside vterm
, and it opens the file in your current Emacs session. I
just use short alias to run e somefile
inside the terminal; it opens
a new buffer for the file.
I have used Emacs for a long time, but these recent additions made it
so much nicer. Hopefully, these are helpful for you too.
As some of you may know, I’ve been a practising Buddhist for some
time, focusing mainly on the teachings of Early Buddhism and
Theravada. One aspect of this practice is keeping up with Uposatha,
which is also present in other schools of Buddhism. Wikipedia
describes Uposatha as
follows:
The Uposatha (Sanskrit: Upavasatha) is a Buddhist day of observance,
in existence from the Buddha’s time (600 BCE), and still being kept
today by Buddhist practitioners. The Buddha taught that the Uposatha
day is for “the cleansing of the defiled mind,” resulting in inner
calm and joy. On this day, both lay and ordained members of the
sangha intensify their practice, deepen their knowledge and express
communal commitment through millennia-old acts of lay-monastic
reciprocity. On these days, the lay followers make a conscious
effort to keep the Five Precepts or (as the tradition suggests) the
ten precepts. It is a day for practicing the Buddha’s teachings and
meditation.
Uposatha days change from lunar month to lunar month, so there are no
set days for those. But generally speaking, Uposatha is observed about
once a week in accordance to the lunar phases. Which lunar phases are
observed depends on the culture, but there are four phases to this:
the new moon, the full moon and two quarter moons between those.
Theravada cultures generally observe all four, but for example, in Sri
Lanka, they tend to only observe the new and the full moon.
How these Uposatha days are calculated is quite interesting, and
there is a paper published on GitHub by Gambhiro Bhikkhu about
that.
Personally, I have followed these moon days by adding those to my
Apple Calendar via the iCal link provided in the repository above, and
I’ve been quite happy with that. Lately, though, I noticed that I tend
to easily miss these days since I don’t often have notifications on or
my calendar open when these days happen, which made me easily miss
those. That got me thinking that it would be pretty cool to have those
lunar phases on my emacs
since that is most likely always open for
me and I’m already mainly doing my time management in org-mode
.
So I started digging around on how these could be easily added to my
org-agenda
, and then I found a built-in command from emacs
that is
at least the first step there called M-x lunar-phases
, which
basically prints lunar phases of the last, current and next month. So
I started hacking around to try to get these to my Agenda view.
Not long after starting my hacking in this, I found a nice doc piece
about some ad-hoc tweaks to be made to the agenda from org-mode
,
which actually made exactly what I wanted. What it involves is that I
need to build an agenda file with these lunar phases so that the
Agenda view in emacs
picks those up.
So first, I needed to add some arbitrary file to my
org-agenda-files
. With use-package
:
(use-package org
:custom
(org-agenda-files '(<your other agenda files> "~/Documents/org/lunar.org")))
Or normally:
(setq org-agenda-files '(<your other agenda files> "~/Documents/org/lunar.org"))
I happen to have all my agenda files in my Documents
folder on my
macOS, so they just get synced across my devices, but naturally, you
can use any path you want.
The lunar.org
file itself is pretty simple. Basically, we add a
header to it with a single diary sexp calling a function,
org-lunar-phases
, which we soon define:
* Lunar phase
#+CATEGORY: Lunar
%%(org-lunar-phases)
The org-lunar-phases
itself either isn’t too complicated. It
involves that we pass in the current day to it, which gets passed in
in a relatively odd way via the diary sexp we used above, and after
that we just parse the lunar-phase-list
with the current day, or
month and year in this case, since lunar-phase-list
returns a list
of lunar phases for the next three months.
(require 'cl-lib)
;; Pass current day to `org-lunar-phases', which is annoyingly in a stupid
:: format, (MM DD YYYY).
(with-no-warnings (defvar date))
(defun org-lunar-phases ()
"Show lunar phase in Agenda buffer."
(require 'lunar)
(let* ((phase-list (lunar-phase-list (nth 0 date) (nth 2 date)))
(phase (cl-find-if (lambda (phase) (equal (car phase) date))
phase-list))
(lunar-phase-names '("● New Moon"
"☽ First Quarter Moon"
"○ Full Moon"
"☾ Last Quarter Moon")))
(when phase
;; Return the phase to the agenda file.
(setq ret (concat (lunar-phase-name (nth 2 phase)))))))
Naturally, you can get all fancy with those lunar-phase-names
. Maybe
adding emojis and whatnot if you’re into it. But in all simplicity,
that is how you can add lunar phases to your agenda, and it shows as
following in there:
10 days-agenda (W08-W09):
Monday 20 February 2023 W08
Lunar: ● New Moon
Tuesday 21 February 2023
Wednesday 22 February 2023
Thursday 23 February 2023
Friday 24 February 2023
Saturday 25 February 2023
Sunday 26 February 2023
Monday 27 February 2023 W09
Lunar: ☽ First Quarter Moon
Tuesday 28 February 2023
Wednesday 1 March 2023
One thing that I noticed from this was the fact the GitHub link above
that I mentioned. That happens to produce a little bit different
results for these phases. Every once in a while, some phases differ
just a tiny bit. There is a mention that the calculation method used
in that paper was related to how these days are calculated in
Mahānikāya in Thailand, so there might be some variance compared to
the M-x lunar-phases
. But personally, I feel it’s close enough and
quite beneficial to me.