Home About Archive RSS

Emacs on Windows

I use Emacs as my text editor. At work, I use a laptop with Windows 11. I use org-present in class for interactive presentations where I can run code blocks and get the results, and write things into the presentation while presenting. Since I sometimes have pictures in my presentations and org-present changes font faces and sizes, I need to use GUI Emacs. There are two ways to get GUI Emacs on Windows. The first is through WSL2 with the graphical features Microsoft calls WSLg.

Unfortunately, the Wayland stack is implemented in a non-standard way in WSLg that doesn't currently work properly. There are two bugs related to Wayland-native programs: the first bug makes Wayland native programs use XWayland instead of Wayland. The community found a fix for that bug in 2023 and told Microsoft about it in a GitHub Issues thread where a representative for Microsoft said they applied the fix to the codebase, but so far, Microsoft has not released that fix. You can fix it yourself if you like. However, there is another bug that makes Wayland windows unresponsive after locking the screen and unlocking it again. This is the show-stopper for WSLg for me. (There is also a similar bug for X windows where the windows disappear after screen locking, but if you open a new X window, you can alt-tab to the original window and use it.)

With WSLg not working properly, I have opted to instead use native Emacs on Windows. I gradually discovered how to get the necessary external packages and how to configure Emacs on Windows to get as similar an experience as possible to the one I have on GNU/Linux. I wrote blog posts as I discovered these things, but I think it is more useful to have all the information in one post, rather than in separate blog posts, so I have deleted those older post and replaced them with this post instead.

I originally kept a separate Emacs configuration for Windows that I forked from my normal config, but since I always wanted to get things to work as similarly as possible on both OSes and I found it hard to keep the common parts in sync, I chose to integrate the two configs back into one with a few if and when-statements when needed. You can find it here.

People use Emacs in different ways, so you may find that you need something I don't, but I hope this blog post can give you a starting point. (It is also useful for me in case I need to set things up again.) Some other good starting points are Using Emacs on Windows 11: An Installation Guide by Dr. Peter Prevos on his LucidManager website and Properly Installing Emacs on Windows, Properly Setting up Emacsclient on Windows, Printing Directly from Emacs on Windows and Getting spelling to work in Emacs on Windows by Dr. Raoul Comninos on the EmacsElements website and YouTube channel. I have used their ideas in my setup when appropriate.

Installation of Emacs on Windows

I use the Windows installer from a nearby mirror of GNU.org as recommended on the Emacs website. After running the installer, I place runemacs.exe on my taskbar and in my startmenu. It is a good idea to use runemacs instead of emacs since emacs opens an additional Windows terminal window before opening the Emacs frame, which runemacs does not. On GNU/Linux, I launch an Emacs server process (also called Emacs daemon) with my window manager which also opens an Emacs frame on my first virtual desktop, but on Windows, I have thus far launched Emacs by hitting Windows+1 since I have runemacs.exe as my leftmost taskbar item. If you would like to use an Emacs server process launched by Windows at startup and just open a frame when you need it, then have a look at Properly Setting up Emacsclient on Windows by Raoul Comninos.

Installation of tools that interact with Emacs

Emacs needs external programs for some of its functionality. Below is a table that shows useful programs I use on Windows to get the functionality I need or want from Emacs. More details can be found in the relevant sections below.

Package What it does Where to get it
chocolatey For installation of (free) software https://chocolatey.org/install Use Individual install.
Hack Free programming font I use as default. https://github.com/source-foundry/Hack-windows-installer
Liberation Free fonts I use for variable-pitch. Chocolatey or LibreOffice
imagemagick For showing pictures Chocolatey
Pandoc Converts file formats Chocolatey
Texlive Export of files from org Install miktex with Chocolatey
mupdf Opens PDFs in doc-view-mode Chocolatey
unoconv Opens MS365 and ODF in doc-view-mode https://www.libreoffice.org/download/download-libreoffice/
git For version control. https://git-scm.com/downloads
hunspell Spell checking program. https://sourceforge.net/projects/ezwinports/hunspell-1.3.2-3-w32.bin.zip/download
nb_NO.aff, nb_NO.dic Norwegian Bokmål dictionary for hunspell. https://www.libreoffice.org/download/download-libreoffice/
Node.js and npm Use NPM to install LSP servers. https://nodejs.org/en/download
Python Use Pip to install LSP servers. Chocolatey
LSP servers Completions, error detection, linting. pip and/or npm

Setup of environment variables

Emacs looks for a .emacs.d directory or a .config/emacs/ directory or a .emacs file in $HOME. To set $HOME, press the Windows key, write environment and you should see the Edit Environment Variables program (Rediger systemmiljøvariabler if you use Norwegian Bokmål). Set the User variables for HOME to wherever you want it. I also found that it was useful for git to set LANG and LC_ALL since git and other programs compiled with MSYS2 respects the text encoding from those. I did this as part of my trial and error process to find ways around git's default conversion of text encoding and line endings, but I am not certain it is necessary after setting the system locale to UTF-8 and/or setting Git Bash's text encoding in the GUI (see the dealing with text-encoding section for more on this).

windowsenv.jpg

Dealing with text-encoding

Windows uses UTF-16 under the hood, but on the surface it uses different text encodings depending on your locale. (GNU/Linux, BSD and MacOS use UTF-8.) Windows uses CR and LF to end lines while GNU/Linux, BSD and MacOS X use LF. (Mac OS < 10 used CR.) There are different strategies to deal with this:

  1. Use the locale's encoding on Windows and have git convert line endings and text encoding on pull and push.
  2. Use UTF-8 and Unix line endings for every buffer and file inside Emacs, but configure Emacs to work with yanking (pasting) and killing (cutting) to and from the clipboard in Windows locale's text encoding. Tell git to not convert files and line endings.
  3. Set Windows 11 to use UTF-8, set Emacs to use UTF-8 and Unix line endings and tell git to use UTF-8 and not convert line endings.

If you do 1, then you don't have to configure git, because that is its default behaviour on Windows. The disadvantage is that your repos will have files with a mix of text encodings if you edit them on different platforms. If you do 2, then you have files with only UTF-8 and Unix line ending in your repos, but you have to configure Emacs to convert text when yanking and killing to/from the system clipboard. You have to configure git not to convert CRLF to LF or the text encoding.

Below is how you configure yanking and killing to work to and from iso-latin-1 which is the common text encoding for Windows in Northern Europe and North America. If not on Windows, I set it to utf-8 so I can use the same configuration on GNU/Linux.

(if (eq system-type 'windows-nt)
   (set-clipboard-coding-system 'iso-latin-1)
  (set-clipboard-coding-system 'utf-8))

My strategy

I use the third strategy now. Windows 11 has a beta feature where you can choose to use UTF-8. You can set it up by following the step-by-step instruction from geekrewind.com. You still have to configure git not to convert CR LF to LF and vise versa on push and pull, but you don't have to configure Emacs to do anything when yanking and killing. To configure git to use UTF, I set the user environment variables LANG and LC_ALL since git and other programs compiled with MSYS2 respects the text encoding from those. In addition, I opened Git Bash, right-clicked the window title, selected Options…, selected Text, chose nb_NO and UTF-8 (you may want something else than nb_NO which is Norwegian Bokmål) since I read somewhere that git uses the settings set in Git Bash. I am not certain if you need to do both, but I did and it works. In my .gitconfig in my $HOME, I made a section called [core] and on the next line wrote autocrlf = false. This means that git will not convert line endings on push and pull from Unix (LF) to Windows (CRLF).

I still have configuration to always use UTF-8 and Unix line endings inside Emacs and save every file as UTF-8 with Unix line endings. This is useful for getting rid of other encodings in previously made files. (In case I want that other encoding, for example for files I want to use under emulation of older systems, I can omit saving and they would still be in their original encoding.) I hope Microsoft will make UTF-8 and Unix line endings standard in Windows 12. The only small problem I have seen since switching to UTF-8 is that a few Norwegian glyphs in HP's notification about driver updates don't display properly. Otherwise, I have had no problems. Below is the configuration I use to set Emacs to use UTF-8 and Unix line endings and write every file with UTF-8 and Unix line endings.

(prefer-coding-system 'utf-8-unix)
(set-default-coding-systems 'utf-8-unix)
(set-language-environment 'utf-8)
(setq-default coding-system-for-write 'utf-8-unix)
(setq-default buffer-file-coding-system 'utf-8-unix)

Spell checking

I used to use aspell with aspell-en and aspell-no or nb depending on whether the distro ships both Nynorsk and Bokmål in the same package or have separate packages (nb = Norwegian Bokmål, nn = Norwegian Nynorsk). I did not find aspell-no or nb on chocolatey or msys2 and I had the same problem with the hunspell package available for Windows which comes with British and American English which I sometimes use, but not Norwegian Bokmål.

For a while, I turned off spelling on Windows, but I recently looked into it again and found that I could copy over the .dic and .aff files I get with LibreOffice since it uses hunspell as well and you get the dictionaries and localisation for your locale when the LibreOffice installer is run on Windows. To use the dictionaries from LibreOffice, go to C:\Program Files\LibreOffice\share\extensions\dict-no\ and copy the files nb_NO.aff and nb_NO.dic. If you use another language, have a look in the extensions folders to find the folder with your relevant dictionaries. Go to the share\hunspell directory under where you installed hunspell. I use C:\Program Files\Hunspell\share\hunspell\ and paste the files there.

In addition I have taken the settings suggested by Getting spelling to work in Emacs on Windows and modified them slightly for my use. I now also use hunspell on GNU/Linux.

(use-package flyspell
  :hook ((prog-mode . flyspell-prog-mode)
         (text-mode . flyspell-mode))
  :init
  (when (eq system-type 'windows-nt)
    (setq ispell-program-name "C:/Program Files/Hunspell/bin/hunspell.exe"))
  (setq ispell-dictionary "nb_NO")
  (setq ispell-dictionary-alist
    '(("nb_NO" ":alpha" "[^[:alpha:]]" "[']" nil ("-d" "nb_NO") nil utf-8)))
  (setq hunspell-default-dict "nb_NO"))

Dictionary lookup

On my GNU/Linux systems, I install a dictd server and some dictionaries so I don't need to be connected to the internet to use dictionary-lookup-definition. Since there is no dictd server package for Windows, I first tried to use dict.org to look up words. When I tried dictionary-lookup-definition, Emacs just froze. It did not use a lot of CPU, but it just did not respond do anything, including C-g. I had to kill it with the three-finger salute (Ctrl-Alt-Delete). Maybe the relevant ports are blocked by my network at work, so it might work for you even if it did not for me. Below, you see my configuration for dictionary mode. I check if I am on GNU/Linux and otherwise, don't load it since I cannot get it to work at work.

(use-package dictionary
   :if (eq system-type 'gnu/linux)
   :config
   (setq dictionary-server "localhost"))

Viewing MS365 and Open Document Format documents

Emacs can show .xlsx, .docx, .pptx, .odt, .odp and other MS365 (Formerly Microsoft Office) and LibreOffice (Open Document Format) files in doc-view-mode. It is very practical to go directly from dired to doc-view-mode in stead of waiting for LibreOffice or MS365 to start. To show these files it needs a python script called unoconv that comes with LibreOffice. You also need to set the path to that script to get it to work.

Pandoc and LaTex for export and import from Org mode

I use pandoc with Emacs and also ox-pandoc to be able to export from Org mode to .docx format. Upper Secondary schools in Norway use MS365, so it is useful to be able to write .docx documents occasionally. I use LaTex a lot for export of PDFs from org files I have used with org-present in class. Both pandoc and LaTex can be installed with Chocolatey, but notice that I had to use miktex to get LaTex via Chocolatey.

Eshell and PowerShell in Shell mode

Eshell is really nice and it works almost as well on Windows as it does on GNU/Linux. It has become my goto shell. The difference between the platforms shows when using "graphical", ie TUI CLI programs, where ansi-term is used to output the output from those programs when on GNU/Linux. There is a difference between a graphical terminal emulator and the shell that runs in it and Eshell is just a shell.

Sometimes I need to use PowerShell to configure WSL2 or Hyper-V or something else that needs to use PowerShell. Since I like to have as few windows open as possible since Windows ironically is terrible at handling windows, it makes more sense to use PowerShell in shell mode and stay within Emacs instead of launching a new PowerShell window. (Some people think window snapping solves the inefficiencies of a floating window manager or desktop environment, but it doesn't since you have to do it manually with a keyboard shortcut or even more inefficiently by mousing around and dragging the windows to the edges. A tiling WM does it automatically which saves time and interruptions.)

By default, you get cmd.exe in Shell mode on Windows. I had some trouble finding good documentation of how to set it up with PowerShell. Some people suggest just setting PowerShell to explicit-shell-file-name, but that only partially worked since it gave me an error that PowerShell was invoked with a wrong argument. A lot of people pointed to someone on the Microsoft blogs that set up PowerShell in Shell mode, but in a way that removed the prompt. He had made an Elisp workaround to get the prompt back, but it seemed like there should be an easier way. The trouble is that comint mode, the mode shell mode is built on, sends -i by default to the shells it interacts with as an argument. I tried a couple of things and it turns out the solution is really simple.

(use-package shell
  :config
  (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
  (add-to-list 'comint-output-filter-functions 'ansi-color-process-output)
  (add-hook 'comint-output-filter-functions 'comint-osc-process-output)
  (add-hook 'shell-mode-hook (lambda() (company-mode 0)))
  (when (eq system-type 'windows-nt)
    (setq explicit-shell-file-name "C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe")
    (setq explicit-powershell.exe-args '("")))
  (when (eq system-type 'gnu/linux)
     (setq explicit-shell-file-name "/bin/bash")
     (setq system-uses-terminfo t)
     (setq comint-terminfo-terminal "xterm")))

In the code block above, if on Windows, I tell Emacs to run PowerShell and give it the arguments "" (ie, nothing) which remove the default -i argument, but adds nothing else. And now I have PowerShell in Shell mode. The rest of the configuration turns off company since the shells have their own tab-completion and turns on ansi-colours which both Bash and PowerShell can work with. If you prefer to use PowerShell 7 instead, use the path to it instead of the path to the built-in Windows PowerShell.

Language Server Protocol servers

LSP is a standard Microsoft made for adding language support to VSCode that is now also used by other editors like NeoVim and Emacs. In Emacs, you just have to have the LSP servers you want installed, the correct paths configured and eglot, company and eldoc, if turned on, makes use of them in the mode for the associated programming or markup language. I tend not to like documentation constantly popping up with eldoc-mode, but use completions and warnings from eglot and company. Below is a table of the LSP servers I use and how to install them on Windows.

Language LSP-server names install with Comment
Python python-lsp-server (pylsp) pip or npm i -g FOSS, flake8 is an optional dependency
Bash / Shell shellcheck, bash-language-server npm i -g FOSS
JavaScript, TS, JSX typescript-language-server, typescript npm i -g Microsoft, LSP depends on typescript
CSS vscode-css-languageserver-bin npm i -g Microsoft
HTML vscode-html-languageserver-bin npm i -g Microsoft
JSON vscode-json-languageserver npm i -g Microsoft

The Microsoft LSP servers give you the same completions, documentation and warnings as in VSCode. You can find more LSP servers at https://langserver.org/.

In addition, you have to turn eglot on in the relevant modes. I turn it on for all programming modes.

(use-package eglot
  :hook (prog-mode . eglot-ensure))  

Python Shell

Python mode in Emacs on GNU/Linux works out of the box with no setup if you have Python installed on your system. However, on Windows, Python mode does not work even if Python is installed. It does not help to add the directory Python is in to the exec-path or add it to the $PATH environment variable either. After trying those two things, I looked for a useful variable to set the path to the python executable and tried a couple of promising ones before finding the correct solution. In the code below, I set the docstring-style to Django which I prefer over the default pep-257 and then check if on Windows and if so, set the path to the shell to where I have python installed.

(setq python-fill-docstring-style 'django)
(when (eq system-type 'windows-nt)
  (setq python-shell-interpreter "c:/Python312/python.exe"))

Paths that need adding

For Emacs to integrate with external packages on Windows, the exec path needs to include the file path to those packages. Below is my extra configuration for adding exec-paths in Windows. You may have placed the programs you have installed manually, like Hunspell, in other places, so adjust accordingly. You may also do this with customize, but I want to have it as part of my config since sometimes I let the custom-vars file where I keep the changes from customize drift a little bit away from the upstream emacs_config repo on some machines.

(when (eq system-type 'windows-nt)
  (custom-set-variables
 '(exec-path
   '("c:/texlive/2024/bin/windows" "C:/Program Files/ImageMagick-7.1.1-Q16-HDRI" "C:/Python312/Scripts/" "C:/Python312/"
     "C:/ProgramData/chocolatey/lib/findutils/tools/install/bin/" "C:/WINDOWS/system32" "C:/WINDOWS" "C:/WINDOWS/System32/Wbem"
     "C:/WINDOWS/System32/WindowsPowerShell/v1.0/" "C:/WINDOWS/System32/OpenSSH/" "C:/Program Files/dotnet/"
     "C:/Program Files/Git/cmd" "C:/Program Files/nodejs/" "C:/ProgramData/chocolatey/bin" "C:/ProgramData/chocolatey/lib/mpv/"
     "C:/ProgramData/chocolatey/lib/mupdf" "C:/Program Files/Cerence_For_Lingit//common" "C:/Program Files/MiKTeX/miktex/bin/x64/"
     "C:/Users/AFK01217/AppData/Local/Microsoft/WindowsApps" "C:/Users/AFK01217/AppData/Local/Programs/Microsoft VS Code/bin"
     "C:/Users/AFK01217/AppData/Roaming/npm" "C:/Program Files/Hunspell/bin" "C:/Users/AFK01217/AppData/Local/Pandoc/" "C:/Python312"
     "." "c:/Strawberry/perl/bin/"))
 '(image-load-path
   '("c:/Program Files/Emacs/emacs-30.1/share/emacs/30.1/etc/images/" data-directory load-path "c:/python312/")))
)
© Einar Mostad 2010 - 2025. Content is licensed under the terms of CC BY SA except code which is GNU GPL v3 or later.