spacemacs icon indicating copy to clipboard operation
spacemacs copied to clipboard

FiraCode “Ligature” integration

Open Profpatsch opened this issue 9 years ago • 63 comments

FiraCode is a nice derivation from Fira Mono that adds many ligatures for a nice code look. It uses Unicode ligatures, but Emacs sadly doesn’t support them (yet). I’d like to add a layer to Spacemacs to support that.

There is a page about integration into Emacs via font-lock, and the second example is also how Hasklig can be made to work with Emacs. I noticed integrating too many ligatures that way makes font rendering very slow.

Profpatsch avatar Aug 31 '16 16:08 Profpatsch

Is there a reason why prettify-symbols-mode is not being used for this?

TheBB avatar Sep 07 '16 17:09 TheBB

Probably not, no. But it looks awesome.

Profpatsch avatar Sep 07 '16 18:09 Profpatsch

I'm not sure if that's the right solution here, as it looks like prettify-symbols-mode alters the number of characters per row, while the ligature rendering preserves that property. For instance, using FiraCode in Konsole would make "->" become an arrow but still take up two characters. Similarly, "::", ">>=", et al. conserve that many characters, keeping the spacing correct. However, if prettify-symbols-mode can keep the character count, then that works too.

GregorySchwartz avatar Sep 23 '16 15:09 GregorySchwartz

So to install and use Fira code with spacemacs, does one follow the instructions for emacs from the fira code project site [1], or there is already some mechanism in spacemacs that can use fira code so that the user only needs to "helm xselect font" the Fira Code Retina? I would very much like to use Fira Code in Spacemacs, but am trying not to break anything. I am just coming from Vim, and know almost nothing about Lisp except what is taught in Wikipedia. I tried putting the first snippet provided in that page to my .spacemacs file within the user-config() function, but after restarting, when I restart spacemacs and type "=>", nothing changes (I do not see a ligature).

[1] https://github.com/tonsky/FiraCode/wiki/Setting-up-Emacs

hyiltiz avatar Feb 21 '17 22:02 hyiltiz

I haven’t yet looked at it any further, maybe the regex-method works, but I suspect it to severely slow down every kind of text display. (It’s essentially running a couple dozen matches on every substring in each buffer…)

Profpatsch avatar Feb 23 '17 11:02 Profpatsch

News! https://ekaschalk.github.io/post/prettify-mode/

It seems to work with firacode now. If anyone wants to take a look: go for it!

Profpatsch avatar Mar 03 '17 20:03 Profpatsch

Another update: Hasklig seems to have moved to the FiraCode mechanism with 1.0 as well. So getting this to work would give us two fonts to choose from.

Profpatsch avatar Mar 06 '17 18:03 Profpatsch

Unfortunately that method still does not allow multiple blocks to be taken up by a single ligature (from the website @Profpatsch linked to). This is problematic for coding:

For ligatures, the number of visual points composing the replacement is the same as its composing characters. For instance, the ligature for -> occupies two spaces.

But this is not the case for prettify-symbols or pretty-mode. Both alpha and not in are reduced to one character.

So the line width you see may not be the same as its actual width.

This has two effects:

A line could then exceed 80 characters with prettify-mode disabled.
Indentation is performed using the Unicode replacements, not actual spacing.

GregorySchwartz avatar Mar 06 '17 18:03 GregorySchwartz

Oh no. Maybe it can be patched?

Profpatsch avatar Mar 06 '17 18:03 Profpatsch

I think that prettify would have to support adding in the lost space.

GregorySchwartz avatar Mar 06 '17 19:03 GregorySchwartz

Well, these fonts make the ligatures exactly as wide as the normal combinations, so it must support multi-width characters as well.

Profpatsch avatar Mar 06 '17 21:03 Profpatsch

Hi I wrote the blog on pretty-mode linked earlier.

The options as I see it are:

  1. Don't use any symbols with disagreeing number of spaces.
  2. Use the visual spacing - your indentation won't agree with the unfontified buffer.
  3. Use the true spacing - your visuals will have extraneous spaces. (Possible with: https://github.com/Ilazki/prettify-utils.el)
  4. Use the visual spacing with true indentation - your visuals will have wrong indentation.
  5. Pre/post-save (or commit/etc..) hook that unfontifies and runs tab on the buffer then refontifies and tabs again.
  6. Use the visual spacing and visually modify indentation, ie. the true indentation is font-locked.

I took a shot at the hook:

(defun test-fontlock-fix-before ()
  (font-lock-mode -1)
  (spacemacs/indent-region-or-buffer))

(defun test-fontlock-fix-after ()
  (font-lock-mode 1)
  (sit-for 1)  ; Without the sleep it doesnt reindent.
  (spacemacs/indent-region-or-buffer))

(add-hook 'before-save-hook 'test-fontlock-fix-before)
(add-hook 'after-save-hook 'test-fontlock-fix-after)

This works in all cases that (spacemacs/indent-region-or-buffer) does, which I found it is not everytime.

The glaring flaws are the sleep and that save-file cannot tell anymore when your file hasn't changed, especially annoying with eg magit.

Number 6 is the only true solution I see to this problem but it seems quite involved.

Any other thoughts on how spacing could be handled?

ekaschalk avatar May 15 '17 23:05 ekaschalk

I implemented the visual spacing solution: https://github.com/Profpatsch/blog/blob/master/posts/ligature-emulation-in-emacs/post.md

I only afterwards noticed that it’s not perfect, but it works fine for me right now with Hasklig.

My idea would be to switch back to non-visual and tag every symbol with the number of spaces it occupies, then prefix them with spaces accordingly. I think I’m gonna try that next.

Profpatsch avatar May 16 '17 11:05 Profpatsch

When I use Fira Code in Spacemacs even without ligature support sometimes the character alignment is off, specifically where line 1 uses bold for some characters, and line 2 doesn't have bold, then they don't line up vertically. What could be causing this?

CMCDragonkai avatar Jul 17 '17 08:07 CMCDragonkai

@Profpatsch did you considered making a MELPA package from your code?

root42 avatar Aug 22 '17 20:08 root42

@root42 No, not really. But if you want one, go ahead. You have my blessing. :) It’s MIT-licensed anyway.

Profpatsch avatar Aug 30 '17 23:08 Profpatsch

@Profpatsch How can I plug that into my .spacemacs?

honza avatar Sep 12 '17 13:09 honza

@honza Just copy the code verbatim, then Spc f e R or Spc r R if that does not work.

Profpatsch avatar Sep 12 '17 14:09 Profpatsch

Putting this code:

  (defun my-correct-symbol-bounds (pretty-alist)
    "Prepend a TAB character to each symbol in this alist,
this way compose-region called by prettify-symbols-mode
will use the correct width of the symbols
instead of the width measured by char-width."
    (mapcar (lambda (el)
              (setcdr el (string ?\t (cdr el)))
              el)
            pretty-alist))

  (defun my-ligature-list (ligatures codepoint-start)
    "Create an alist of strings to replace with
codepoints starting from codepoint-start."
    (let ((codepoints (-iterate '1+ codepoint-start (length ligatures))))
      (-zip-pair ligatures codepoints)))

  (setq my-fira-code-ligatures
        (let* ((ligs '("www" "**" "***" "**/" "*>" "*/" "\\\\" "\\\\\\"
                       "{-" "[]" "::" ":::" ":=" "!!" "!=" "!==" "-}"
                       "--" "---" "-->" "->" "->>" "-<" "-<<" "-~"
                       "#{" "#[" "##" "###" "####" "#(" "#?" "#_" "#_("
                       ".-" ".=" ".." "..<" "..." "?=" "??" ";;" "/*"
                       "/**" "/=" "/==" "/>" "//" "///" "&&" "||" "||="
                       "|=" "|>" "^=" "$>" "++" "+++" "+>" "=:=" "=="
                       "===" "==>" "=>" "=>>" "<=" "=<<" "=/=" ">-" ">="
                       ">=>" ">>" ">>-" ">>=" ">>>" "<*" "<*>" "<|" "<|>"
                       "<$" "<$>" "<!--" "<-" "<--" "<->" "<+" "<+>" "<="
                       "<==" "<=>" "<=<" "<>" "<<" "<<-" "<<=" "<<<" "<~"
                       "<~~" "</" "</>" "~@" "~-" "~=" "~>" "~~" "~~>" "%%"
                       "x" ":" "+" "+" "*")))
          (my-correct-symbol-bounds (my-ligature-list ligs #Xe100))))

to the (defun dotspacemacs/user-config ()) section, and restarting Emacs has no effect. I also set the font to Fira Code.

honza avatar Sep 13 '17 18:09 honza

You forgot the code that adds it to the hooks that are called when you enable a mode.

  ;; nice glyphs for haskell with hasklig
  (defun my-set-hasklig-ligatures ()
    "Add hasklig ligatures for use with prettify-symbols-mode."
    (setq prettify-symbols-alist
          (append my-hasklig-ligatures prettify-symbols-alist))
    (prettify-symbols-mode))

  (add-hook 'haskell-mode-hook 'my-set-hasklig-ligatures)

For FiraCode change accordingly.

Profpatsch avatar Sep 13 '17 19:09 Profpatsch

@Profpatsch Ah, thanks! I was hoping to enable it globally.

honza avatar Sep 13 '17 19:09 honza

I can confirm that works wonderfully. Thanks @Profpatsch <3

honza avatar Sep 13 '17 19:09 honza

Afaik inherits every mode from fundamental-mode and every programming language mode from prog-mode. So (maybe?) hooking it into one of these makes it work globally.

Profpatsch avatar Sep 13 '17 20:09 Profpatsch

I've developed a proof of concept fix to ligature indentation that you all might find interesting: http://www.modernemacs.com/post/lig-spacing/

ekaschalk avatar Oct 06 '17 18:10 ekaschalk

http://www.modernemacs.com/post/lig-spacing/

Won’t that require support from language modes, who can tell you how many lines down spaces should be collapsed?

The ligature code in this issue is all based on the assumption that ligatures have exactly the same width as the naïve code, which is true of all ligatures in FiraCode or Hasklig.

Profpatsch avatar Oct 07 '17 00:10 Profpatsch

Won’t that require support from language modes

It's major mode agnostic.

which is true of all ligatures in FiraCode or Hasklig.

FiraCode and Hasklig aren't the only fonts to implement ligatures - see PragmataPro.

Spaced ligatures do not imply correct visual indentation/grouping. For example, start a form with the .- ligature or use the +++ ligature and check an org-table.

This is as much a post about the more general prettify-mode which not only has incorrect visual, but also incorrect true indentation. If the restriction surrounding incorrect true indentation is removed, then ligature/symbol-based programming becomes more reasonable.

ekaschalk avatar Oct 07 '17 15:10 ekaschalk

I tried this for both Fira Code and Hasklig, but my output is definitely unexpected:

2017-10-10-093723_958x1078_scrot

2017-10-10-093822_958x1078_scrot

I've placed the code from here as-is into my user-config, and my font settings look like:

   dotspacemacs-default-font '("Hasklig"
                               :size 13
                               :weight normal
                               :width normal
                               :powerline-scale 1.75)

Any ideas?

fosskers avatar Oct 10 '17 16:10 fosskers

Interestingly, if I switch my font back to Source Code Pro but leave in the custom elisp, the weird replacements from above still show. So, I know that Spacemacs can see Hasklig, since changing it to say Hasklig Foo and restarting throws warnings, but just Hasklig does not. I installed Hasklig from an Arch Linux AUR package. I also know that the ligature replacement mechanism is in place, but for some reason it's not finding the right ligatures.

fosskers avatar Oct 10 '17 16:10 fosskers

@fosskers which version of Hasklig do you have installed? They changed the way ligatures worked after 1.0 I think, currently I use 1.1.

Profpatsch avatar Oct 10 '17 17:10 Profpatsch

I've got 1.1 from this package.

fosskers avatar Oct 10 '17 17:10 fosskers