Exploring my Emacs packages - web-mode

Project homepage : http://web-mode.org/

For a long time html-mode was the best option for web editing in Emacs. Although it works well for straight html, it can struggle when in template languages.

Packages like mumamo and mmm-mode make it possible to mix major modes together, but I haven't had much success with them.

web-mode is a mode made specifically for editing HTML with embedded templates. It supports the following languages:

  • ctemplate (for mustache, handlebars, ember etc)
  • django/liquid/twig
  • erb
  • lsp
  • php

And 15 more (at the time of writing).

It's one of the modes I use most, so it makes sense to learn some more about it.

Adding a file extension to web-mode

To make Emacs use web-mode for a specific file extension, add the following to .emacs.d/init.el (or another file where your configuration lives).

;; Make Emacs use web-mode for .phtml files.
(add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))

Navigation key bindings

C-c C-n
Jump between opening and closing tags. The cursor has to be on the tag (not the tag content) for this to work.
C-c C-e b
Jump to the beginning of the current element.
C-c C-e b
Jump to the end of the current element.

Region selection key bindings

C-c C-e s
Select the current element (opening through closing).
C-c C-m
Expand the current selection to the nearest element. I prefer to use expand region, but this is still quite useful.

Other useful key bindings

C-c C-e c
Clone the current element under the cursor. Useful when filling something like a table and wanting to replicate a column.
C-c C-f
Fold or unfold the current block.
M-;
Comment the current line or region. Automatically works with whatever template language is currently under the cursor.

Per-project indentation

Different projects may have different coding styles. Some require tabs, some require 4 spaces and some require 2 spaces.

Thankfully Emacs supports per-directory settings via dir-locals.el. The following code will set a project to use tabs instead of spaces, and to display tab width as 2 characters.

((web-mode . ((indent-tabs-mode . t)
	      (tab-width . 2))))

This example will use 4 spaces for indentation.

((web-mode . ((indent-tabs-mode . nil)
	      (web-mode-markup-indent-offset . 4))))

Snippets

C-c C-s will insert a snippet at the current location. An example snippet looks a bit like this:

(setq web-mode-extra-snippets
      '(("php" . (("foreach" . "<?php foreach ( $items as $item ) : ?>\n(|)\n<?php endforeach; ?>")))))

Which inserts the following and places the cursor at the | character:

<?php foreach ( $items as $item ) : ?>
|
<?php endforeach ?>

I prefer to use emmet-mode for generating HTML quickly and yasnippet for other modes, so this isn't something I have much experience with.


This post is part of the "Exploring my Emacs packages" series.


Exploring my Emacs packages - yasnippet

Project homepage : http://joaotavora.github.io/yasnippet/

yasnippet is a template expansion library for Emacs. It's a great way to quickly insert code without doing a lot of typing. For example:

Simple expansion example

yasnippet also supports user-entered variables in snippets. They look a little bit like this:

Snippet expansion with variables

Previous versions of the extension came bundled with snippets, but they are no longer included with the main package. The yasnippet-snippets repository on GitHub contains templates for over 50 major modes.

Basic snippet development

Every snippet is contained in a file of its own. To create a new snippet, call M-x yas-new-snippet to open a new buffer that is set up for snippet development. C-c C-c will save the new snippet.

The snippet code for the second screenshot looks like this:

# -*- mode: snippet -*-
# name: Wrap a helper or model
# key: wrap_helper
# group: templates
# --
protected function $1() {
	if ( empty( $this->$1 ) ) {
		$this->$1 = new ${2:ClassName}();
	}

	return $this->$1;
}
$0

The $1 and ${2:ClassName} are replaced by user input when the snippet is expanded. ClassName is inserted as a placeholder until replaced by the user.

Snippet files start with a small amount of metadata, most of which is for organization. There is also an optional condition field that executes elisp to check if the snippet should be expanded or not. This can be helpful to prevent snippet insertion in certain places.

Complex snippets using elisp

Snippets can also execute elisp code contained between ` characters. For example, the current time can be inserted in a template by using `(current-time-string)`.

It takes a little while to get the hang of, but it can be used for a lot of different things:

  • Inserting the name of the current file.
  • Transforming user input. This can be used to generate ID's.
  • Prompting for input from a set list of choices via yas-choose-value.
  • Using when and unless to insert additional content depending on user input.

A very simple example of when snippet logic looks a little like this:

# key: how_am_i
Current emotion: $1

${1:$(when (string= ":D" yas-text) "You are VERY happy")}
${1:$(when (string= ":)" yas-text) "You are happy")}
${1:$(when (string= ":(" yas-text) "You are sad")}

and does this:

Snippet expansion with elisp logic

By using embedded elisp, the original snippet from screenshot 2 can be changed to a single input that is formatted automatically:

# -*- mode: snippet -*-
# name: Wrap a helper or model
# key: wrap_helper
# group: template
# --
protected function ${1:$(string-inflection-underscore-function yas-text)} {
	if ( empty( $this->${1:$(string-inflection-underscore-function yas-text)} ) ) {
		$this->${1:$(string-inflection-underscore-function yas-text)} = new ${1:ClassName}();
	}

	return $this->${1:$(string-inflection-underscore-function yas-text)};
}
$0

The ClassName parameter (which can be accessed in elisp via yas-text) is automatically converted to underscores using string-inflection-underscore-function.

Snippet expansion with embedded elisp

There are also functions for working with regions, fields and user input. The yasnippet homepage contains a in-depth guide to snippet development along with a complete function reference.


This post is part of the "Exploring my Emacs packages" series.


Groundhog Day Resolutions - October 2019

September was a tricky month, but I think it went quite well.

September's Primary Goals

1. Run a half marathon

Running a half marathon was the biggest goal I set myself for the year. I'm not an athletic person and I knew running would be a challenge for me. I set myself three times to beat, with 2:30 being the absolute quickest I felt I could do if I really pushed myself.

I finished in 2:23, better than I ever imagined I could do.

Preparation was a big part of this goal. Running was the main prep, but I also planned out meals and tested various combinations of running gear. By race day I knew exactly what to wear, what to eat before and after, and what to do for recovery. It helped make the day go much smoother and eliminated most worries during the actual run.

2. Complete two secondary goals

I released two new BlitzMax tools: blam and docgen. Blam is a build tool that I use for all my BlitzMax projects. docgen is a fairly small project that extracts documentation from source code so I don't have to type it out twice.

3. Write two chapters of my book

Two more chapters are now ready to be cleaned up and edited. The book is still just a pile of notes and rough pages at this point, but it's slowly starting to coalesce.

September's Secondary Goals

1. Write at least 3 blog posts

I didn't write as much as I wanted to, but I wrote enough to pass here.

Primary Goals for October

1. Complete the MVP of my book

This won't be the finished version, and it will still need editing, but I want the basic structure and chapters ready by November 11th.

2. Complete FOUR secondary goals

There are 13 secondary goals left for the year and 82 days to complete them in. I really need to start crossing off more goals if I want everything done by December 31st.


Exploring my Emacs packages - company-mode

What is company-mode?

Project homepage : https://github.com/company-mode/company-mode

company-mode adds auto-completion to the current buffer in Emacs. It's similar to ac-mode and supports some more modern features, such as lsp (language server protocol).

It works well out of the box, but with some additional packages and per-language configuration it can really shine.

Example packages for web development

company-web
Adds completion for web-mode, and can be used with HTML and HTM templates, although I prefer using emmet-mode for creating elements.
company-flow
JavaScript completion. Requires flow to be installed.
robe
Auto-completion for Ruby which uses a Ruby REPL to get code information. Requires the file (or project in Rails) to be reloaded when code changes.
ac-php
Adds auto-completion for common PHP extensions, as well as user defined classes and functions. It also supports annotations and type hints. It requires a special tags file to be created in order to get the most out of it, and this file should be regenerated when code changes.

Using lsp

company-lsp-mode.png

lsp is a common protocol for language servers. lsp-mode is a package that allows Emacs to communicate with lsp servers, and it supports a bunch of different languages.

Using lsp requires some extra setup as each language has its own server implementation which may need to be installed and configured. However, once it's up and running it's a fairly smooth experience.

company-lsp integrates with lsp-mode and allows company-mode to use information from running lsp servers.

Although it takes a while to configure, using lsp removes the need to regenerate tag files or reload projects when code changes.


This post is part of the "Exploring my Emacs packages" series.


Groundhog Day Resolutions - September 2019

August's GHD goals went much better than July. I'm still way behind on my 2019 goals, but I made a slight dent which has helped with motivation.

August's Primary Goals

1. Keep on running

I picked up a couple of injuries playing soccer (including a nice scar) so I decided to play it safe a few times. I still managed to run more miles than any previous month, and I tried a couple of trail runs that were way, way steeper than I expected.

2. Use a schedule every week

I've been planning my days since setting this goal. I haven't always stuck to it, but so far it's kept me fairly focused.

3. Complete at least one secondary goal

I migrated this site to a new server, set up automated builds and added functionality for scheduling posts. Everything is configured via ansible which is something I wanted to learn.

4. Write a chapter of my book

Although I didn't write as much as I wanted, I did manage to complete a chapter. I'll probably edit it again in the future, but for now I'm happy with some progress.

August's Secondary Goals

1. Read another book

I read "The Life-Changing Magic of Tidying Up" by Marie Kondo. It's quite a breezy read considering it's about tidying up, and a lot of it reminded me of the GTD process, such as performing an initial inbox sweep and making sure things have a specific place.

2. Plan a diet and recovery plan for the half marathon

The short version: Eat carbs for a few days before the race. Avoid fat and fiber the night before to reduce the risk of stomach ache.

Primary Goals for September

1. Run a half marathon

My half marathon is scheduled for the first week of October so it fits into September's goals.

To say I'm nervous is an understatement.

2. Complete two secondary goals

There are a couple of projects I've been working on that just need a little push to be complete.

3. Write two chapters of my book

At this point the only thing that is going to get this book done is work. I have my chapter plan and notes, now it's just time to make it readable.

Secondary Goals for September

1. Write at least 3 blog posts

I've slipped out of the habit of writing regularly as the year has gone on. I'd like to get back in a rhythm and publish at least 3 posts in September.

Thoughts on August

One lesson I really tried to take from "The Motivation Hacker" was the use of success spirals. The idea is to complete lots of very small actions to build motivation.

Rather than trying to get all of my goals done in the first week (or the last week like I normally do) I tried to break them into small steps that I could do every day. This strategy worked really well for writing (which is normally something I dread).

Scheduling tasks also helped enormously, although I wasn't as disciplined with times as I should have been.

I'm still way behind on my goals, but I'm hoping once The Run is out of the way I'll have more time (and energy) to focus on them.