diff --git a/flake.lock b/flake.lock
index f776c4e..a38a49e 100644
--- a/flake.lock
+++ b/flake.lock
@@ -22,11 +22,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1619558193,
-        "narHash": "sha256-DljP5/9EX0eXEPhzCUFqFEHkkcFuXJBx1PTgcv0OgyM=",
+        "lastModified": 1620138697,
+        "narHash": "sha256-8Mgj+Fj4zGEI7oA9wbyqvdwq+46kAyd3barMIedWkho=",
         "owner": "nix-community",
         "repo": "home-manager",
-        "rev": "18ad12d52b8cebbb57013865eec2be5125de050a",
+        "rev": "64c5228c0828fff0c94c1d42f7225115c299ae08",
         "type": "github"
       },
       "original": {
@@ -38,11 +38,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1619464443,
-        "narHash": "sha256-R7WAb8EnkIJxxaF6GTHUPytjonhB4Zm0iatyWoW169A=",
+        "lastModified": 1620074890,
+        "narHash": "sha256-4Z8Zwpg0gPvqKbSsck1g9ql4E5NClGZdjyxbYoaXA4s=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "8e4fe32876ca15e3d5eb3ecd3ca0b224417f5f17",
+        "rev": "7cb76200088f45cd24a9aa67fd2f9657943d78a4",
         "type": "github"
       },
       "original": {
@@ -69,11 +69,11 @@
     },
     "nur": {
       "locked": {
-        "lastModified": 1619617127,
-        "narHash": "sha256-A5+xGeftQfhcjMVGqWFYfjZZX1EaBBqkirVQe1ZyaXk=",
+        "lastModified": 1620161416,
+        "narHash": "sha256-GoKpxMWJrBMkVTvoKfRFedu7K+wUrbZUuMqTearjiu8=",
         "owner": "nix-community",
         "repo": "NUR",
-        "rev": "96572c099b8c13b520e764a2a8f516bc81ffcb2c",
+        "rev": "ead34572175dc6de3b96b7ad6996315f84bede90",
         "type": "github"
       },
       "original": {
@@ -145,11 +145,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1619616710,
-        "narHash": "sha256-NIXeFf3zKsULIOx61P/vVsS30BD2YoshduSw680nJkM=",
+        "lastModified": 1619976063,
+        "narHash": "sha256-2tjVP95+qR1UzL8/UeV/xRJu7S2fRB7rf7enZedbrik=",
         "owner": "cachix",
         "repo": "pre-commit-hooks.nix",
-        "rev": "c33f92d9d69a73c77061e4fd47234662625cbdf9",
+        "rev": "09fb9e425111878b58223852e87ed85e8a189e0d",
         "type": "github"
       },
       "original": {
diff --git a/home/editors/neovim/default.nix b/home/editors/neovim/default.nix
index 44812f5..240e418 100644
--- a/home/editors/neovim/default.nix
+++ b/home/editors/neovim/default.nix
@@ -2,12 +2,12 @@
 
 let
   neovim-unwrapped = pkgs.neovim-unwrapped.overrideAttrs (oldAttrs: rec {
-    version = "2021-04-13";
+    version = "2021-05-04";
     src = pkgs.fetchFromGitHub {
       owner = "neovim";
       repo = "neovim";
-      rev = "c9817603cff5a5ca7ef4e4c12c8bf0e16d859e9a";
-      sha256 = "10ynpvrdm7sdkvmv96rgk0a6xa7489v65pxshml99vd4h7x8cfzl";
+      rev = "63d8a8f4e8b02e524d85aed08aa16c5d9815598c";
+      sha256 = "0zfrbvj8f2993n1gy37cnfmgixi6zgickzf44c1ll888k5f5rrx3";
     };
     nativeBuildInputs = oldAttrs.nativeBuildInputs
       ++ [ pkgs.utf8proc pkgs.tree-sitter ];
@@ -17,23 +17,23 @@ let
 
   nvim-ts-autotag = buildVimPluginFrom2Nix {
     pname = "nvim-ts-autotag";
-    version = "2021-03-26";
+    version = "2021-04-25";
     src = pkgs.fetchFromGitHub {
       owner = "windwp";
       repo = "nvim-ts-autotag";
-      rev = "00b79b593af52585809e341c8dd1b7d839b3c466";
-      sha256 = "0a17jp6xn9vb8f205ivsw1fx1bapg6l5ywl1miacc06py459c7ch";
+      rev = "3d96e14e4400ce56e4fe0bf9b5e2e64b69dd7e65";
+      sha256 = "1ay93fak6m7x06ik8f4km00ln92l7cmlfmknms9czl2sl4pnrvzq";
     };
   };
 
   nvim-ts-context-commentstring = buildVimPluginFrom2Nix {
     pname = "nvim-ts-context-commentstring";
-    version = "2021-04-06";
+    version = "2021-04-17";
     src = pkgs.fetchFromGitHub {
       owner = "JoosepAlviste";
       repo = "nvim-ts-context-commentstring";
-      rev = "5024c83e92c3988f6e7119bfa1b2347ae3a42c3e";
-      sha256 = "13k7gwbrkbxjb3bf98zv6b64qqlas8z8xr9hxlr5l5hwmz5gw83i";
+      rev = "03a9c64d0b4249d91fd371de48bf3f6ac8a22d33";
+      sha256 = "1d4yygrz05vnp24bszwncajcksnkg66x0qks7y5398rr675kzl2g";
     };
   };
 
@@ -43,11 +43,7 @@ let
     EOF
   '';
 in {
-  home.packages = with pkgs; [
-    gcc # required for nvim-treesitter
-    tree-sitter
-    graphviz
-  ];
+  home.packages = with pkgs; [ graphviz ];
 
   programs.neovim = {
     enable = true;
@@ -66,9 +62,8 @@ in {
       vim-surround
       vim-commentary
       vim-easymotion
-      vim-which-key
+      which-key-nvim
       vim-fugitive
-      registers-nvim
       plenary-nvim
       gitsigns-nvim
       vim-test
@@ -77,10 +72,7 @@ in {
       wmgraphviz-vim
       nvim-compe
 
-      # use :TSInstall & :TSUpdate to manage parsers
-      nvim-treesitter
-      # TODO https://github.com/NixOS/nixpkgs/pull/115445
-      # nvim-treesitter.withPlugins (builtins.attrValues pkgs.tree-sitter.builtGrammars)
+      (nvim-treesitter.withPlugins (_: pkgs.tree-sitter.allGrammars))
       nvim-treesitter-context
       nvim-treesitter-refactor
       nvim-treesitter-textobjects
@@ -94,12 +86,12 @@ in {
     ];
     extraConfig = with builtins;
       readFile ./init.vim # + readFile ./vim-surround-fix.vim
-      + readFile ./which-key.vim + readFile ./test.vim
-      + vimLua (readFile ./gitsigns.lua)
-      + vimLua (readFile ./lsp/extensions.lua) + readFile ./lsp/lsp.vim
-      + vimLua (readFile ./lsp/lsp.lua) + vimLua (readFile ./treesitter.lua);
+      + vimLua (readFile ./which-key.lua) + vimLua (readFile ./gitsigns.lua)
+      + readFile ./test.vim + vimLua (readFile ./lsp/extensions.lua)
+      + readFile ./lsp/lsp.vim + vimLua (readFile ./lsp/lsp.lua)
+      + vimLua (readFile ./treesitter.lua);
     withNodeJs = false;
-    withPython = false;
+    withPython3 = false;
   };
 
   xdg.configFile."nvim/filetype.vim".source = ./filetype.vim;
diff --git a/home/editors/neovim/init.vim b/home/editors/neovim/init.vim
index 44f05d9..70e0270 100644
--- a/home/editors/neovim/init.vim
+++ b/home/editors/neovim/init.vim
@@ -95,7 +95,6 @@ set splitbelow
 map ; :Files<CR>
 nmap <C-p> :NERDTreeToggle<CR>
 " nmap <silent> <C-p> :Lexplore<CR>
-map <Leader> <Plug>(easymotion-prefix)
 
 let g:netrw_banner = 0
 let g:netrw_liststyle = 3
diff --git a/home/editors/neovim/lsp/lsp.lua b/home/editors/neovim/lsp/lsp.lua
index 3564120..16cb999 100644
--- a/home/editors/neovim/lsp/lsp.lua
+++ b/home/editors/neovim/lsp/lsp.lua
@@ -1,9 +1,9 @@
 local config = require'lspconfig'
 
 -- format on save
--- TODO often takes way longer to save than 1000 ms (e.g. 7000 ms in fitnesspilot-web)
 local diagnosticls_on_attach = function(_, bufnr)
-   vim.api.nvim_command("au BufWritePre <buffer> lua vim.lsp.buf.formatting_sync(nil, 1000)")
+  vim.api.nvim_command(
+    "au BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync(nil, nil, { 'tsserver', 'diagnosticls' })")
 end
 
 local pid = vim.fn.getpid()
@@ -18,7 +18,7 @@ config.dockerls.setup{}
 config.rnix.setup{}
 config.tsserver.setup{}
 config.omnisharp.setup{
-  cmd = {"omnisharp", "--languageserver", "--hostPID", tostring(pid)};
+  cmd = {"omnisharp", "--languageserver", "--hostPID", tostring(pid)},
 }
 config.pyls.setup{}
 config.terraformls.setup{}
@@ -27,126 +27,135 @@ config.hls.setup{}
 -- based on: https://github.com/mikew/vimrc/blob/master/src/nvim/coc-settings.json
 -- TODO breaks auto-completion when using with other lsp
 -- TODO some ts projects are using tsc eslint plugin
--- config.diagnosticls.setup{
---   on_attach = diagnosticls_on_attach;
---   filetypes = {
---     "javascript",
---     "javascript.jsx",
---     "javascriptreact",
---     "typescript",
---     "typescript.jsx",
---     "typescriptreact",
---     "json",
---     "yaml",
---     "markdown",
---     "html",
---     "css"
---   };
---   init_options = {
---     linters = {
---       eslint = {
---         command = "eslint";
---         args = {
---           "--stdin",
---           "--stdin-filename",
---           "%filepath",
---           "--format",
---           "json"
---         };
---         rootPatterns = {".git"};
---         debounce = 50;
---         sourceName = "eslint";
---         parseJson = {
---           errorsRoot = "[0].messages";
---           line = "line";
---           column = "column";
---           endLine = "endLine";
---           endColumn = "endColumn";
---           message = "${message} [${ruleId}]";
---           security = "severity";
---         };
---         securities = {
---           ["2"] = "error";
---           ["1"] = "warning";
---         };
---       };
---       stylelint = {
---         command = "stylelint";
---         args = {
---           "--stdin",
---           "--formatter",
---           "json",
---           "--file",
---           "%filepath"
---         };
---         rootPatterns = {".git"};
---         debounce = 50;
---         sourceName = "stylelint";
---         parseJson = {
---           errorsRoot = "[0].warnings";
---           line = "line";
---           column = "column";
---           message = "${text}";
---           security = "severity";
---         };
---         securities = {
---           error = "error";
---           warning = "warning";
---         };
---       };
---     };
---     filetypes = {
---       javascript = {"eslint"};
---       ["javascript.jsx"] = {"eslint"};
---       javascriptreact = {"eslint"};
---       typescript = {"eslint"};
---       ["typescript.jsx"] = {"eslint"};
---       typescriptreact = {"eslint"};
---       css = {"stylelint"};
---     };
---     formatters = {
---       eslint = {
---         command = "eslint";
---         args = {
---           "--fix",
---           "%file"
---         };
---         rootPatterns = {".git"};
---         isStdout = 1;
---         doesWriteToFile = 1;
---       };
---       stylelint = {
---         command = "stylelint";
---         args = {
---           "--stdin",
---           "--fix",
---           "--file",
---           "%filepath"
---         };
---         rootPatterns = {".git"};
---       };
---       prettier = {
---         command = "prettier";
---         args = {
---           "--stdin",
---           "--stdin-filepath",
---           "%filepath"
---         };
---         rootPatterns = {".git"};
---       };
---     };
---     formatFiletypes = {
---       javascript = {"eslint"};
---       ["javascript.jsx"] = {"eslint"};
---       javascriptreact = {"eslint"};
---       typescript = {"eslint"};
---       ["typescript.jsx"] = {"eslint"};
---       typescriptreact = {"eslint"};
---       json = {"prettier"};
---       yaml = {"prettier"};
---       markdown = {"prettier"};
---       html = {"prettier"};
---       css = {"stylelint"};
---     };
---   };
--- }
+config.diagnosticls.setup{
+  on_attach = diagnosticls_on_attach,
+  filetypes = {
+    "javascript",
+    "javascript.jsx",
+    "javascriptreact",
+    "typescript",
+    "typescript.jsx",
+    "typescriptreact",
+    "json",
+    "yaml",
+    "markdown",
+    "html",
+    "css"
+  },
+  init_options = {
+    linters = {
+      eslint = {
+        command = "eslint_d",
+        args = {
+          "--cache",
+          "--stdin",
+          "--stdin-filename",
+          "%filepath",
+          "--format",
+          "json"
+        },
+        rootPatterns = {".eslintrc.js", ".eslintrc.json", ".git"},
+        debounce = 50,
+        sourceName = "eslint",
+        parseJson = {
+          errorsRoot = "[0].messages",
+          line = "line",
+          column = "column",
+          endLine = "endLine",
+          endColumn = "endColumn",
+          message = "${message} [${ruleId}]",
+          security = "severity"
+        },
+        securities = {
+          ["2"] = "error",
+          ["1"] = "warning"
+        },
+      },
+      stylelint = {
+        command = "stylelint",
+        args = {
+          "--stdin",
+          "--formatter",
+          "json",
+          "--file",
+          "%filepath"
+        },
+        rootPatterns = {".git"},
+        debounce = 50,
+        sourceName = "stylelint",
+        parseJson = {
+          errorsRoot = "[0].warnings",
+          line = "line",
+          column = "column",
+          message = "${text}",
+          security = "severity",
+        },
+        securities = {
+          error = "error",
+          warning = "warning",
+        },
+      },
+    },
+    filetypes = {
+      javascript = {"eslint"},
+      ["javascript.jsx"] = {"eslint"},
+      javascriptreact = {"eslint"},
+      typescript = {"eslint"},
+      ["typescript.jsx"] = {"eslint"},
+      typescriptreact = {"eslint"},
+      css = {"stylelint"},
+    },
+    formatters = {
+      eslint = {
+        command = "eslint_d",
+        args = {
+          -- TODO eslint_d's stdin option doesn't work for some reason
+          -- "--cache",
+          -- "--fix-to-stdout",
+          -- "--stdin",
+          -- "--stdin-filename",
+          -- "%filepath"
+          "--cache",
+          "--fix",
+          "%filepath"
+        },
+        debounce = 50,
+        rootPatterns = {".eslintrc.js", ".eslintrc.json", ".git"},
+        isStdout = false,
+        doesWriteToFile = true,
+      },
+      stylelint = {
+        command = "stylelint",
+        args = {
+          "--stdin",
+          "--fix",
+          "--file",
+          "%filepath"
+        },
+        rootPatterns = {".stylelintrc.json", ".git"},
+      },
+      prettier = {
+        command = "prettier",
+        args = {
+          "--stdin",
+          "--stdin-filepath",
+          "%filepath"
+        },
+        rootPatterns = {".prettierrc.json", ".git"},
+      },
+    },
+    formatFiletypes = {
+      javascript = {"eslint"},
+      ["javascript.jsx"] = {"eslint"},
+      javascriptreact = {"eslint"},
+      typescript = {"eslint"},
+      ["typescript.jsx"] = {"eslint"},
+      typescriptreact = {"eslint"},
+      json = {"prettier"},
+      yaml = {"prettier"},
+      markdown = {"prettier"},
+      html = {"prettier"},
+      css = {"stylelint"},
+    },
+  },
+}
diff --git a/home/editors/neovim/lsp/lsp.vim b/home/editors/neovim/lsp/lsp.vim
index ac63994..d944183 100644
--- a/home/editors/neovim/lsp/lsp.vim
+++ b/home/editors/neovim/lsp/lsp.vim
@@ -32,7 +32,7 @@ nnoremap <silent> K          <cmd>lua vim.lsp.buf.hover()<CR>
 nnoremap <silent> <c-k>      <cmd>lua vim.lsp.buf.signature_help()<CR>
 nnoremap <silent> <leader>sd <cmd>lua vim.lsp.buf.document_symbol()<CR>
 nnoremap <silent> <leader>sw <cmd>lua vim.lsp.buf.workspace_symbol()<CR>
-nnoremap <silent> <leader>f  <cmd>lua vim.lsp.buf.formatting()<CR>
+nnoremap <silent> <leader>f  <cmd>lua vim.lsp.buf.formatting_seq_sync(nil, nil, { 'tsserver', 'diagnosticls' })<CR>
 nnoremap <silent> <leader>a  <cmd>lua vim.lsp.buf.code_action()<CR>
 nnoremap <silent> <leader>r  <cmd>lua vim.lsp.buf.rename()<CR>
 nnoremap <silent> <leader>d  <cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>
diff --git a/home/editors/neovim/which-key.lua b/home/editors/neovim/which-key.lua
new file mode 100644
index 0000000..cede01d
--- /dev/null
+++ b/home/editors/neovim/which-key.lua
@@ -0,0 +1 @@
+require("which-key").setup { }
diff --git a/home/editors/neovim/which-key.vim b/home/editors/neovim/which-key.vim
deleted file mode 100644
index 03c8bc4..0000000
--- a/home/editors/neovim/which-key.vim
+++ /dev/null
@@ -1,9 +0,0 @@
-nnoremap <silent> <leader>      :<c-u>WhichKey '<Space>'<CR>
-vnoremap <silent> <leader>      :<c-u>WhichKeyVisual '<Space>'<CR>
-nnoremap <silent> <localleader> :<c-u>WhichKey ','<CR>
-vnoremap <silent> <localleader> :<c-u>WhichKeyVisual ','<CR>
-
-nnoremap <silent> <leader><leader>           :<c-u>WhichKey '<Space>'<CR>
-vnoremap <silent> <leader><leader>           :<c-u>WhichKeyVisual '<Space>'<CR>
-nnoremap <silent> <localleader><localleader> :<c-u>WhichKey ','<CR>
-vnoremap <silent> <localleader><localleader> :<c-u>WhichKeyVisual ','<CR>