syl20bnr / spacemacs

A community-driven Emacs distribution - The best editor is neither Emacs nor Vim, it's Emacs *and* Vim!
http://spacemacs.org
GNU General Public License v3.0
23.58k stars 4.9k forks source link

FiraCode “Ligature” integration #6972

Open Profpatsch opened 7 years ago

Profpatsch commented 7 years ago

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.

TheBB commented 7 years ago

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

Profpatsch commented 7 years ago

Probably not, no. But it looks awesome.

GregorySchwartz commented 7 years ago

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.

hyiltiz commented 7 years ago

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

Profpatsch commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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.

GregorySchwartz commented 7 years ago

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.
Profpatsch commented 7 years ago

Oh no. Maybe it can be patched?

GregorySchwartz commented 7 years ago

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

Profpatsch commented 7 years ago

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

ekaschalk commented 7 years ago

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?

Profpatsch commented 7 years ago

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.

CMCDragonkai commented 6 years ago

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?

root42 commented 6 years ago

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

Profpatsch commented 6 years ago

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

honza commented 6 years ago

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

Profpatsch commented 6 years ago

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

honza commented 6 years ago

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.

Profpatsch commented 6 years ago

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.

honza commented 6 years ago

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

honza commented 6 years ago

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

Profpatsch commented 6 years ago

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.

ekaschalk commented 6 years ago

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

Profpatsch commented 6 years ago

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.

ekaschalk commented 6 years ago

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.

fosskers commented 6 years ago

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 commented 6 years ago

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.

Profpatsch commented 6 years ago

@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.

fosskers commented 6 years ago

I've got 1.1 from this package.

Profpatsch commented 6 years ago

Hm, the PKGBUILD seems to do exactly the same stuff the nixpkgs build file does. Maybe try rebuilding your fontconfig caches? Have you logged out or rebooted after installing the font?

fosskers commented 6 years ago

Success! I just needed to reboot my machine. Things look great, thanks for your help.

fosskers commented 6 years ago

Works out-of-the-box for Scala too! Just add:

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

to your user-config.

fazilz commented 6 years ago

@Profpatsch @honza so I added the following code to .spacemacs:

(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))))
  ;; nice glyphs for haskell with hasklig
  (defun my-set-fira-code-ligatures ()
    "Add fira-code ligatures for use with prettify-symbols-mode."
    (setq prettify-symbols-alist
          (append my-fira-code-ligatures prettify-symbols-alist))
    (prettify-symbols-mode))

  (add-hook 'prog-mode-hook 'my-set-fira-code-ligatures)

and I get something that looks like this:

screen shot 2017-11-27 at 7 46 11 pm

any ideas as to how i can fix this? thanks!

Profpatsch commented 6 years ago

@fazilz Have you rebooted? (You don’t have to reboot if you know how to rebuild your fontconfig cache.)

fazilz commented 6 years ago

@Profpatsch yes I have rebooted. also cleared font caches by running (not sure how to rebuild fontconfig cache though)

sudo atsutil databases -remove

still the same result

fazilz commented 6 years ago

I poked around a little bit and found that fontconfig cache lives in

/opt/local/var/cache

(If you installed it via macports) and deleted the cache and restarted but still get weird symbols. interestingly enough lambda gets rendered properly.

screen shot 2017-11-28 at 2 24 33 pm

Sorry for spamming like this I really want to make this thing work.

Profpatsch commented 6 years ago

(If you installed it via macports)

Ah, can’t help you on Darwin, sorry.

fazilz commented 6 years ago

Ah, can’t help you on Darwin, sorry.

If I solve this somehow, I'll post the solution here, thanks for the original code!

gustavAR commented 6 years ago

I got this working on macOS by following the instructions from this issue.

  1. Installing emacs-mac
  2. Adding the following to my dotspacemacs/user-config:
    (if (eq system-type 'darwin)
      (mac-auto-operator-composition-mode))
  3. Changing font in dotspacemacs/init:
    dotspacemacs-default-font '("Fira Code"
                            :size 12
                            :weight normal
                            :width normal
                            :powerline-scale 1.1)

    The www and x ligatures don't work, since they aren't operators.

fazilz commented 6 years ago

thank you so muc @gustavAR also emacs-mac seems to be a lot faster than emacs-app from macports so thats a nice side effect.

rlopzc commented 6 years ago

@gustavAR when I start spacemacs it says: (Spacemacs) Error in dotspacemacs/user-config: Symbol’s function definition is void: mac-auto-operator-composition-mode

screen shot 2018-02-22 at 10 04 04
gustavAR commented 6 years ago

@romariolopezc That is the exact error you get if you aren't running emacs-mac. Are you sure you are running emacs-mac? If so, the top of your window should be black like this: image As opposed to the normal border which looks like this: image

rlopzc commented 6 years ago

@gustavAR I installed emacs-mac, but I think I'm not running emacs-mac. How do I run it? Isn't just open spacemacs? I have installed emacs-plus

I already had spacemacs installed. Do I need to uninstall, and then install emacs-mac?

gustavAR commented 6 years ago

emacs-mac doesn't override your old emacs installation automatically.

Homebrew has probably installed emacs-mac in /usr/local/opt/emacs-mac/. so you're going to want to create an alias of Emacs.app by right-clicking it: image Move the alias to your Applications folder and make sure it's the only one. I tried doing this automatically by using brew linkapps, but for some reason this command prioritised my pre-existing installation of emacs-plus.

You might also want to make the emacs command refer to your new installation. If so, add an alias to your ~/.bashrc, which can be done by running the following command in your terminal and restarting your terminals.

echo "alias emacs=\"/usr/local/opt/emacs-mac/bin/emacs\"" >> ~/.bashrc
rlopzc commented 6 years ago

Fixed! Thanks @gustavAR 👍

Btw now my themes are displaying correctly, and is faster than before!

abeluck commented 6 years ago

This issue is quite long and there are many comments, some specific to macOS and some not.

As a debian spacemacs user, is there a known solution/workaround to make FiraCode work in (spac)emacs with ligatures not affecting line length?

fosskers commented 6 years ago

@abeluck Have you tried this and followed the conversation for a few messages from this timestamp?

Huxpro commented 5 years ago

@gustavAR As far as I know, emacs-mac and emacs-plus were two different distributions of Emacs on mac. Any caveats of switching from one to the another? I couldn't find any detailed comparison between both sadly.

mdedetrich commented 5 years ago

I made a ticket about this, i.e. a specific implementation already exists https://github.com/syl20bnr/spacemacs/issues/11639