From 79069cbb4b8b2ac1a86fe44e5a2000e12c78b1fe Mon Sep 17 00:00:00 2001 From: <> Date: Tue, 17 Oct 2023 15:54:04 +0000 Subject: [PATCH] Deployed 6b9f971 with MkDocs version: 1.5.3 --- 404.html | 26 +- api-tools/index.html | 26 +- assets/images/social/index.html | 26 +- .../language-server-protocol.png | Bin 39887 -> 0 bytes .../refactor-tools.png | Bin 0 -> 46644 bytes .../structural-editing.png | Bin 0 -> 46952 bytes .../astronvim/config-design/index.html | 26 +- configuration/astronvim/index.html | 26 +- configuration/index.html | 26 +- .../practicalli/config-design/index.html | 26 +- configuration/practicalli/index.html | 26 +- configuration/practicalli/packages/index.html | 26 +- .../practicalli/packages/lualine/index.html | 26 +- .../packages/nvim-treesitter/index.html | 26 +- configuration/practicalli/packer/index.html | 26 +- index.html | 26 +- install/clojure/index.html | 26 +- install/index.html | 26 +- install/neovim/index.html | 26 +- introduction/community-projects/index.html | 26 +- introduction/contributing/index.html | 26 +- introduction/features/index.html | 26 +- introduction/fennel/index.html | 26 +- introduction/repl-workflow/index.html | 26 +- introduction/writing-tips/index.html | 26 +- neovim-basics/comments/index.html | 26 +- .../file-buffer-window-tab/index.html | 26 +- neovim-basics/index.html | 26 +- neovim-basics/multi-modal-editing/index.html | 26 +- neovim-basics/multiple-cursors/index.html | 26 +- neovim-basics/notifications/index.html | 28 +- neovim-basics/plugin-manager/index.html | 26 +- neovim-basics/search-replace/index.html | 26 +- neovim-basics/snippets/index.html | 26 +- neovim-basics/terminal/index.html | 26 +- neovim-basics/zen-mode/index.html | 26 +- reference/lua-language/index.html | 26 +- reference/neovim/index.html | 26 +- .../neovim/language-providers/index.html | 26 +- reference/neovim/standard-path/index.html | 26 +- reference/vim-style/case/index.html | 26 +- reference/vim-style/g-menu/index.html | 26 +- reference/vim-style/index.html | 26 +- .../key-binding-reference/index.html | 26 +- reference/vim-style/motions/index.html | 26 +- reference/vim-style/moving-around/index.html | 26 +- reference/vim-style/narrowing/index.html | 26 +- reference/vim-style/speaking-vim/index.html | 26 +- .../vim-style/vim-quick-reference/index.html | 26 +- .../vim-tips-for-developers/index.html | 26 +- reference/vim-style/visual-select/index.html | 26 +- reference/vim-style/z-menu/index.html | 26 +- repl-driven-development/conjure/index.html | 150 +- repl-driven-development/index.html | 95 +- .../index.html | 99 +- .../structural-editing/index.html | 2764 +++++++++++++++++ repl-driven-development/testing/index.html | 141 +- search/search_index.json | 2 +- sitemap.xml | 7 +- sitemap.xml.gz | Bin 792 -> 797 bytes termux/clojure-development/index.html | 26 +- termux/custom-shell/index.html | 26 +- termux/fdroid-install/index.html | 26 +- termux/git/index.html | 26 +- termux/index.html | 26 +- termux/neovim/index.html | 26 +- termux/setup/index.html | 26 +- termux/using-termux/index.html | 26 +- version-control/diff/index.html | 26 +- version-control/index.html | 26 +- version-control/lazygit/index.html | 26 +- version-control/neogit/index.html | 26 +- version-control/octo/index.html | 26 +- version-control/open-in-github/index.html | 26 +- 74 files changed, 4620 insertions(+), 278 deletions(-) delete mode 100644 assets/images/social/repl-driven-development/language-server-protocol.png create mode 100644 assets/images/social/repl-driven-development/refactor-tools.png create mode 100644 assets/images/social/repl-driven-development/structural-editing.png rename repl-driven-development/{language-server-protocol => refactor-tools}/index.html (96%) create mode 100644 repl-driven-development/structural-editing/index.html diff --git a/404.html b/404.html index fb499e8a..0cff99b6 100644 --- a/404.html +++ b/404.html @@ -1283,6 +1283,8 @@ + + @@ -1352,11 +1354,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/api-tools/index.html b/api-tools/index.html index 565e1ec8..89093baa 100644 --- a/api-tools/index.html +++ b/api-tools/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/assets/images/social/index.html b/assets/images/social/index.html index 8cb44ff7..beb7220f 100644 --- a/assets/images/social/index.html +++ b/assets/images/social/index.html @@ -1322,6 +1322,8 @@ + + @@ -1391,11 +1393,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/assets/images/social/repl-driven-development/language-server-protocol.png b/assets/images/social/repl-driven-development/language-server-protocol.png deleted file mode 100644 index b95e6742526f6445b0995a03e39dfc9a686e2502..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39887 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�lki?Z^h-%{L7m%^>$%}PiiA`r!)iB&eastGZM)A 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 diff --git a/assets/images/social/repl-driven-development/refactor-tools.png b/assets/images/social/repl-driven-development/refactor-tools.png new file mode 100644 index 0000000000000000000000000000000000000000..0223b53af1af71d4527b2a56acea80bc9ae7938f GIT binary patch literal 46644 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%0D7;?u^h)DSpN#AZTFBxoK)(FRrPW9KG-6 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 literal 0 HcmV?d00001 diff --git a/assets/images/social/repl-driven-development/structural-editing.png b/assets/images/social/repl-driven-development/structural-editing.png new file mode 100644 index 0000000000000000000000000000000000000000..bd584e9d167d1d012fc858886185341987c2fbe7 GIT binary patch literal 46952 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 NXz-8qKV10r{{a6U?@a&z literal 0 HcmV?d00001 diff --git a/configuration/astronvim/config-design/index.html b/configuration/astronvim/config-design/index.html index a8961ee2..9d88a70e 100644 --- a/configuration/astronvim/config-design/index.html +++ b/configuration/astronvim/config-design/index.html @@ -1459,6 +1459,8 @@ + + @@ -1528,11 +1530,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/configuration/astronvim/index.html b/configuration/astronvim/index.html index 9def0eaf..5f54e1c2 100644 --- a/configuration/astronvim/index.html +++ b/configuration/astronvim/index.html @@ -1330,6 +1330,8 @@ + + @@ -1399,11 +1401,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/configuration/index.html b/configuration/index.html index 76c2169b..933141bf 100644 --- a/configuration/index.html +++ b/configuration/index.html @@ -1328,6 +1328,8 @@ + + @@ -1397,11 +1399,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/configuration/practicalli/config-design/index.html b/configuration/practicalli/config-design/index.html index 2c519412..a83b903f 100644 --- a/configuration/practicalli/config-design/index.html +++ b/configuration/practicalli/config-design/index.html @@ -1459,6 +1459,8 @@ + + @@ -1528,11 +1530,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/configuration/practicalli/index.html b/configuration/practicalli/index.html index b192426f..c0dd7d95 100644 --- a/configuration/practicalli/index.html +++ b/configuration/practicalli/index.html @@ -1330,6 +1330,8 @@ + + @@ -1399,11 +1401,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/configuration/practicalli/packages/index.html b/configuration/practicalli/packages/index.html index 4c0fdfd7..df3d81c5 100644 --- a/configuration/practicalli/packages/index.html +++ b/configuration/practicalli/packages/index.html @@ -1332,6 +1332,8 @@ + + @@ -1401,11 +1403,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/configuration/practicalli/packages/lualine/index.html b/configuration/practicalli/packages/lualine/index.html index caa71f29..3582ac79 100644 --- a/configuration/practicalli/packages/lualine/index.html +++ b/configuration/practicalli/packages/lualine/index.html @@ -1379,6 +1379,8 @@ + + @@ -1448,11 +1450,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/configuration/practicalli/packages/nvim-treesitter/index.html b/configuration/practicalli/packages/nvim-treesitter/index.html index 37abdbe0..ed109058 100644 --- a/configuration/practicalli/packages/nvim-treesitter/index.html +++ b/configuration/practicalli/packages/nvim-treesitter/index.html @@ -1386,6 +1386,8 @@ + + @@ -1455,11 +1457,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/configuration/practicalli/packer/index.html b/configuration/practicalli/packer/index.html index 590b5e57..9089e105 100644 --- a/configuration/practicalli/packer/index.html +++ b/configuration/practicalli/packer/index.html @@ -1384,6 +1384,8 @@ + + @@ -1453,11 +1455,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/index.html b/index.html index b51ed1ce..3edd3a00 100644 --- a/index.html +++ b/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/install/clojure/index.html b/install/clojure/index.html index e3c7f099..a5023c13 100644 --- a/install/clojure/index.html +++ b/install/clojure/index.html @@ -1389,6 +1389,8 @@ + + @@ -1458,11 +1460,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/install/index.html b/install/index.html index 60bf865f..e6d8454f 100644 --- a/install/index.html +++ b/install/index.html @@ -1328,6 +1328,8 @@ + + @@ -1397,11 +1399,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/install/neovim/index.html b/install/neovim/index.html index 98aec6f4..3c35080b 100644 --- a/install/neovim/index.html +++ b/install/neovim/index.html @@ -1389,6 +1389,8 @@ + + @@ -1458,11 +1460,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/introduction/community-projects/index.html b/introduction/community-projects/index.html index df209306..7ffb48e4 100644 --- a/introduction/community-projects/index.html +++ b/introduction/community-projects/index.html @@ -1389,6 +1389,8 @@ + + @@ -1458,11 +1460,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/introduction/contributing/index.html b/introduction/contributing/index.html index dd840a1e..88b0b311 100644 --- a/introduction/contributing/index.html +++ b/introduction/contributing/index.html @@ -1396,6 +1396,8 @@ + + @@ -1465,11 +1467,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/introduction/features/index.html b/introduction/features/index.html index af0f52ad..c05fe1a0 100644 --- a/introduction/features/index.html +++ b/introduction/features/index.html @@ -1445,6 +1445,8 @@ + + @@ -1514,11 +1516,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/introduction/fennel/index.html b/introduction/fennel/index.html index 533ac85c..a2229839 100644 --- a/introduction/fennel/index.html +++ b/introduction/fennel/index.html @@ -1403,6 +1403,8 @@ + + @@ -1472,11 +1474,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/introduction/repl-workflow/index.html b/introduction/repl-workflow/index.html index b407e780..fdd6af18 100644 --- a/introduction/repl-workflow/index.html +++ b/introduction/repl-workflow/index.html @@ -1431,6 +1431,8 @@ + + @@ -1500,11 +1502,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/introduction/writing-tips/index.html b/introduction/writing-tips/index.html index 8b08e53e..e513d7d5 100644 --- a/introduction/writing-tips/index.html +++ b/introduction/writing-tips/index.html @@ -1512,6 +1512,8 @@ + + @@ -1581,11 +1583,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/comments/index.html b/neovim-basics/comments/index.html index ada34694..9e9fbf4f 100644 --- a/neovim-basics/comments/index.html +++ b/neovim-basics/comments/index.html @@ -1338,6 +1338,8 @@ + + @@ -1407,11 +1409,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/file-buffer-window-tab/index.html b/neovim-basics/file-buffer-window-tab/index.html index b48211ca..bba97058 100644 --- a/neovim-basics/file-buffer-window-tab/index.html +++ b/neovim-basics/file-buffer-window-tab/index.html @@ -1443,6 +1443,8 @@ + + @@ -1512,11 +1514,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/index.html b/neovim-basics/index.html index 5eed3696..31d11e11 100644 --- a/neovim-basics/index.html +++ b/neovim-basics/index.html @@ -1328,6 +1328,8 @@ + + @@ -1397,11 +1399,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/multi-modal-editing/index.html b/neovim-basics/multi-modal-editing/index.html index 89747a36..7f9ba97b 100644 --- a/neovim-basics/multi-modal-editing/index.html +++ b/neovim-basics/multi-modal-editing/index.html @@ -1422,6 +1422,8 @@ + + @@ -1491,11 +1493,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/multiple-cursors/index.html b/neovim-basics/multiple-cursors/index.html index fc041d14..b3eaa90b 100644 --- a/neovim-basics/multiple-cursors/index.html +++ b/neovim-basics/multiple-cursors/index.html @@ -1514,6 +1514,8 @@ + + @@ -1583,11 +1585,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/notifications/index.html b/neovim-basics/notifications/index.html index ba039966..ad9c4a78 100644 --- a/neovim-basics/notifications/index.html +++ b/neovim-basics/notifications/index.html @@ -1375,6 +1375,8 @@ + + @@ -1444,11 +1446,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools @@ -2547,6 +2569,8 @@

    Notifications

    Space f n lists the history of notifications for the current sesion

    Enter to open the highlighted item in the list in its own popup

    +

    AstroNvim Notifications history +AstroNvim Notifications history

    Notification popups show information, warnings and errors.

    nvim-notify example

    Configure notificationsλ︎

    diff --git a/neovim-basics/plugin-manager/index.html b/neovim-basics/plugin-manager/index.html index 5aa10f33..a7dc8ac2 100644 --- a/neovim-basics/plugin-manager/index.html +++ b/neovim-basics/plugin-manager/index.html @@ -1382,6 +1382,8 @@ + + @@ -1451,11 +1453,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/search-replace/index.html b/neovim-basics/search-replace/index.html index c1c833a8..f1f539a1 100644 --- a/neovim-basics/search-replace/index.html +++ b/neovim-basics/search-replace/index.html @@ -1338,6 +1338,8 @@ + + @@ -1407,11 +1409,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/snippets/index.html b/neovim-basics/snippets/index.html index 730ea98a..87f64f80 100644 --- a/neovim-basics/snippets/index.html +++ b/neovim-basics/snippets/index.html @@ -1375,6 +1375,8 @@ + + @@ -1444,11 +1446,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/terminal/index.html b/neovim-basics/terminal/index.html index 292123d7..15061892 100644 --- a/neovim-basics/terminal/index.html +++ b/neovim-basics/terminal/index.html @@ -1338,6 +1338,8 @@ + + @@ -1407,11 +1409,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/neovim-basics/zen-mode/index.html b/neovim-basics/zen-mode/index.html index ae254a52..b8878ac4 100644 --- a/neovim-basics/zen-mode/index.html +++ b/neovim-basics/zen-mode/index.html @@ -1389,6 +1389,8 @@ + + @@ -1458,11 +1460,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/lua-language/index.html b/reference/lua-language/index.html index b8ddd37b..38ff57fd 100644 --- a/reference/lua-language/index.html +++ b/reference/lua-language/index.html @@ -1324,6 +1324,8 @@ + + @@ -1393,11 +1395,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/neovim/index.html b/reference/neovim/index.html index 03e8a5e1..5d9204e3 100644 --- a/reference/neovim/index.html +++ b/reference/neovim/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/neovim/language-providers/index.html b/reference/neovim/language-providers/index.html index 26ad33da..d9d7eeb6 100644 --- a/reference/neovim/language-providers/index.html +++ b/reference/neovim/language-providers/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/neovim/standard-path/index.html b/reference/neovim/standard-path/index.html index 0e5649f7..fdc0ca48 100644 --- a/reference/neovim/standard-path/index.html +++ b/reference/neovim/standard-path/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/case/index.html b/reference/vim-style/case/index.html index a8f1daf3..ec22ed32 100644 --- a/reference/vim-style/case/index.html +++ b/reference/vim-style/case/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/g-menu/index.html b/reference/vim-style/g-menu/index.html index 6e2a9f2d..e9545162 100644 --- a/reference/vim-style/g-menu/index.html +++ b/reference/vim-style/g-menu/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/index.html b/reference/vim-style/index.html index 2bfc3480..4662644b 100644 --- a/reference/vim-style/index.html +++ b/reference/vim-style/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/key-binding-reference/index.html b/reference/vim-style/key-binding-reference/index.html index d9891977..de36ff43 100644 --- a/reference/vim-style/key-binding-reference/index.html +++ b/reference/vim-style/key-binding-reference/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/motions/index.html b/reference/vim-style/motions/index.html index 8f07c327..67a3ebc8 100644 --- a/reference/vim-style/motions/index.html +++ b/reference/vim-style/motions/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/moving-around/index.html b/reference/vim-style/moving-around/index.html index 3dba750a..e1f733ed 100644 --- a/reference/vim-style/moving-around/index.html +++ b/reference/vim-style/moving-around/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/narrowing/index.html b/reference/vim-style/narrowing/index.html index 3760713b..b1973b82 100644 --- a/reference/vim-style/narrowing/index.html +++ b/reference/vim-style/narrowing/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/speaking-vim/index.html b/reference/vim-style/speaking-vim/index.html index 989b746b..5a3ff8d3 100644 --- a/reference/vim-style/speaking-vim/index.html +++ b/reference/vim-style/speaking-vim/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/vim-quick-reference/index.html b/reference/vim-style/vim-quick-reference/index.html index 9a5fb24e..9beb7f72 100644 --- a/reference/vim-style/vim-quick-reference/index.html +++ b/reference/vim-style/vim-quick-reference/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/vim-tips-for-developers/index.html b/reference/vim-style/vim-tips-for-developers/index.html index 6c568a23..ed6ba071 100644 --- a/reference/vim-style/vim-tips-for-developers/index.html +++ b/reference/vim-style/vim-tips-for-developers/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/visual-select/index.html b/reference/vim-style/visual-select/index.html index 5c60bedf..1535b430 100644 --- a/reference/vim-style/visual-select/index.html +++ b/reference/vim-style/visual-select/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/reference/vim-style/z-menu/index.html b/reference/vim-style/z-menu/index.html index df912a3d..f4d1633d 100644 --- a/reference/vim-style/z-menu/index.html +++ b/reference/vim-style/z-menu/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
  • - + + + + + Structural Editing + + + + +
  • + + + + + + + + + +
  • + - LSP + Refactor Tools diff --git a/repl-driven-development/conjure/index.html b/repl-driven-development/conjure/index.html index b9c2ea3a..a3c21a21 100644 --- a/repl-driven-development/conjure/index.html +++ b/repl-driven-development/conjure/index.html @@ -26,7 +26,7 @@ - + @@ -1328,6 +1328,8 @@ + + @@ -1423,8 +1425,8 @@
    • - - Fundamentals + + Start REPL
    • @@ -1437,8 +1439,8 @@
    • - - REPL buffer (log) + + REPL log
    • @@ -1465,11 +1467,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools @@ -2525,8 +2547,8 @@ @@ -2573,7 +2609,7 @@

      Unit tests and test runners Practicalli sets Kaocha test runner as default

      🌐 practicalli/neovim-config-redux sets Kaocha as the default test runner

      @@ -2604,41 +2640,98 @@

      Unit tests and test runnersInclude test pathλ︎

      -

      Ensure the test directory is included in the classpath when starting a REPL. Use a project or user-level alias that includes an :extra-paths key that includes ["test"] path

      -
      clojure -M:env/test:lib/reloaded:repl/rebel
      +

      Ensure the test directory is included in the classpath when starting a REPL. Use a project or user level alias which defines an :extra-paths key with the ["test"] path

      +
      +
      clojure -M:test/env:repl/reloaded
       
      +

      Conjure Test runnersλ︎

      , t n to run the tests for the current namespace

      +

      , t a to run all tests in the project

      Neovim - Conjure Clojure unit test runner with test results in HUD

      External test runnerλ︎

      Open a terminal in Neovim or a separate terminal session to run start a test runner in watch mode. Tests run automatically when the code changes are saved

      -

      Include the :env/test alias to include additional paths and dependencies for the test environment configuration, e.g. using additional libraries to run the tests such as mocking or human readable output.

      -
      clojure -X:env/test:test/watch
      +
      +
      +
      +

      Practicalli Clojure CLI config contains aliases for test runner tools

      +
        +
      • +

        :test/run uses Kaocha to run all tests, stopping on first failing test. Add :fail-fast? false argument to run all tests regardless of failure

        +
      • +
      • +

        :test/watch as above and puts Kaocha in watch mode, triggering a test run each time a file is saved

        +
      • +
      +

      Projects created with Practicalli Project Templates include a test and test-watch task to run Kaocha test runner

      +
      +

      Run all tests, stoping on first failing test

      +
      make test
       
      +
      +
      +

      Watch for changes and run all tests, stoping on first failing test

      +
      make test-watch
      +
      +
      +

      The make tasks call Clojure CLI with the appropriate alias, e.g. clojure -X:test/run and clojure -X:test/watch

      +
      +
      +

      Kaocha test runner for Clojure in watch mode

      -
      -

      Refine the tests that are watched

      -

      Start the watcher with focused or skiped tests by name or meta data (test selectors)

      +

      Test Selectorsλ︎

      +

      Use Test selectors to run a sub-set of tests based on selector meta data added to deftest code

      +
      (deftest ^:infrastructure function-name-test
      +  (testing ""
      +    (is ,,,))
      +
      +(deftest ^:persistence function-name-test
      +  (testing ""
      +    (is ,,,))
      +
      +
      +
      +
      +

      Kaocha test runner can focus or skip on a sub-set of unit tests using test id, metadata, namespaces or a specific deftest.

      +
        +
      • :focus or :skip a given namespace or specific test var, i.e. deftest
      • +
      • :focus-meta or :skip-meta test selectors (metadata) on test vars, i.e. ^:persistence
      • +
      +

      Specifying test :id in the tests.edn configuration file allows different test suites to be run, e.g. :unit for unit tests, :spec for specification tests

      +

      Focus and skip works with a single test run or with a continuous watcher.

      +
      +

      Skip all tests with :persistence metadata

      +
      clojure -X:test/watch :skip-meta :persistence
      +
      -

      Test selectors to run a sub-set of tests based on selector meta data added to deftest code

      -
      clojure -X:test/watch :skip-meta :persistence
      +
      +

      Focus on a specific test namespace

      +
      clojure -X:test/watch :focus '["practicalli.gameboard.api.scoreboard-test"]
       
      -
      -

      TODO: check syntax of Kaocha test selectors

      -
      -

      Using Kaocha, specific test namespaces (or specific tests) can be run (or excluded)

      -
      clojure -X:test/watch :namespace '[practicalli.server"]
      +
      +
      +

      Focus on a specific unit test (deftest)

      +
      clojure -X:test/watch :focus '["practicalli.gameboard.api.scoreboard-test/total-score-test"]
       
      -
      -

      TODO: check syntax of Kaocha namespace and specific tests

      -
      +
      +
      +

      Refine the tests that are watched

      +

      Start the watcher with 🌐 focused or skiped tests by name or meta data (test selectors)

      +
      +
      +
      +

      Cognitect Labs Test Runner can include or exclude a sub-set of tests, identified by metadata on the var (deftest)

      +

      Cognitect Labs Test Runner - inclusions & exclusions

      +
      +
      +

      Last update: - July 23, 2023 + October 17, 2023 @@ -2674,7 +2767,7 @@

      External test runner - +

      diff --git a/search/search_index.json b/search/search_index.json index 04c7d43c..9e51328e 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Neovim for Clojure development","text":"

      coding at the speed of thought

      Neovim is incredibly fast and efficient, so thoughts flow from brain to editor without unnecessary delay.

      Touch typing is fast, Neovim and multi-modal editing makes it even faster.

      Neovim is a highly extensible and powerful editor, supporting multi-modal editing and Vim-style sequential key bindings. Highly responsive and low resource use makes Neovim ideal for development on any computer or mobile device, e.g. tablet, smartphone.

      Neovim has a diverse set of plugins and Practicalli curated configurations use these plugins to provide a rich set of features for Clojure development and wider engineering tasks.

      Practicalli Neovim provides install & user guide focused on a simple, powerful and satisfying REPL Driven workflow for Clojure.

      "},{"location":"#quick-start","title":"Quick Start","text":"

      Install Clojure, Neovim and choose a community configuration

      • [Lua] AstroNvim
      • [Fennel] Practicalli Neovim Config Redux
      "},{"location":"#external-reverences","title":"External reverences","text":"

      Getting started with Neovim and Conjure

      Neovim user guide

      This Week In Neovim - community update

      Conjure install guide Conjuring Clojure in Vim

      "},{"location":"#navigate-the-book","title":"Navigate the book","text":"

      Use the mouse or built-in key bindings to navigate the pages of the book

      • P , , : go to previous page
      • N , . : go to next page

      Use the search box to quickly find a specific topic

      • F , S , / : open search dialog
      • Down , Up : select next / previous result
      • Esc , Tab : close search dialog
      • Enter : follow selected result
      "},{"location":"#sponsor-my-work","title":"Sponsor my work","text":"

      All sponsorship recieved is used to maintain and further develop the Practicalli series of books and videos, although most of the work is still done with my own time and cost.

      Thank you to Cognitect, Nubank and a wide range of other sponsors from the Clojure community for your continued support

      "},{"location":"#creative-commons-license","title":"Creative commons license","text":"This work is licensed under a Creative Commons Attribution 4.0 ShareAlike License (including images & style sheets)."},{"location":"api-tools/","title":"API Tools","text":"

      Astrocommunity proivdes plugins to support working with APIs and the JSON format

      • nvim-jqx
      • rest-nvim

      Included in Practicalli Astronvim Config

      Practicalli Astronvim Config includes nvim-jqx and rest.nvim plugins

      "},{"location":"api-tools/#inspect-json","title":"Inspect JSON","text":"

      Browse and preview json files in neovim.

      :JqxList prettify JSON and start the inspector

      JqxQuery to run complex jq commands

      jq binary required

      jq binary should be available on the command line as nvim-jqx runs jq queries internally

      nvim-jqx

      "},{"location":"api-tools/#call-apis","title":"Call APIs","text":"

      Space r r to run an http request under the cursor from within an *.http file.

      A fast Neovim http client written in Lua, providing a curl wrapper.

      "},{"location":"api-tools/#http-file","title":"http file","text":"

      Open a file with an *.http extension

      Write a call to an API, e.g. a call to a local server health care endpoint

      Call locally running API

      health-check.http
      GET http://localhost:8080/system-admin/status\n

      A new window opens with the result of the API call

      Result of API call with rest.nvim
      GET http://localhost:8080/system-admin/status\nCommand :curl -sSL --compressed -X 'GET' --data-raw '' 'http://localhost:8080/system-admin/status'\n#+END\nHTTP/1.1 200 OK\nContent-Type: application/json; charset=utf-8\nContent-Length: 66\nServer: http-kit\nDate: Mon, 10 Jul 2023 16:21:33 GMT\n\n#+RESPONSE\n{\"application\":\"practicalli hole-in-one Service\",\"status\":\"Alive\"}\n#+END\n

      The Content-Type can be explicitly set, especially useful when not using JSON

      API call returning EDN data

      GET http://localhost:8080/api/v1/scoreboard\naccept: application/edn\n

      rest.nvim test examples

      rest.nvim

      "},{"location":"assets/images/social/","title":"Social Cards","text":"

      Social Cards are visual previews of the website that are included when sending links via social media platforms.

      Material for MkDocs is configured to generate beautiful social cards automatically, using the colors, fonts and logos defined in mkdocs.yml

      Generated images are stored in this directory.

      "},{"location":"configuration/","title":"Neovim Configuration","text":"

      Practicalli Neovim covers the following configurations.

      • AstroNvim - thoughtful configuration, supports Neovim 0.9 onward, polished UI, many community extensions
      • Practicalli Neovim Config Redux - mnemonic key bindings, packer, telescope selector, written in Fennel
      "},{"location":"configuration/#multiple-configurations","title":"Multiple Configurations","text":"

      Install multiple configurations, e.g. AstroNvim, lazyvim, Nvchad, etc. in the $HOME/.config directory using unique directory names.

      Set NVIM_APPNAME to specific the configuration to use when running nvim.

      NVIM_APPNAME=astronvim nvim\n

      NVIM_APPNAME variable should be set to the directory name containing the configuration, relative to the .config directory.

      The configuration directory name is used to hold share, state and cache files for that specific configuration.

      Create shell aliases for each configuration. Optionalliy, define a terminal UI selection to choose a configuration.

      Shell AliasesTerminal UI Selector

      Create a Shell alias for each configuration that will be used, to avoid setting the NVIM_APPNAME variable each time.

      Define Shell Aliases to run each configuration

      alias astro=\"NVIM_APPNAME=astronvim nvim\"\nalias lazyvim=\"NVIM_APPNAME=lazyvim nvim\"\nalias practicalli-redux=\"NVIM_APPNAME=neovim-config-redux nvim\"\n

      Create an nvim configuration selector script, with items listing the directory name of each configuration

      Z Shell nvim-selector script

      .local/bin/nvim-selector
      function nvim-selector() {\n  items=(\"astronvim\" \"neovim-config-redux\" \"lazyvim\")\n  config=$(printf \"%s\\n\" \"${items[@]}\" | fzf --prompt=\"\ue62b Neovim Config \uf63d \" --height=~50% --layout=reverse --border --exit-0)\n  if [[ -z $config ]]; then\n    echo \"Nothing selected\"\n    return 0\n  elif [[ $config == \"default\" ]]; then\n    config=\"\"\n  fi\n  NVIM_APPNAME=$config nvim $@\n}\n
      "},{"location":"configuration/astronvim/","title":"AstroNvim","text":"

      AstroNvim is a community configuration with an engaging UI, using Lazy for plugin management (Neovim packages) and Mason for package management (LSP, DAP, format and lint tools)

      Practicalli AstroNvim Config is a user configuration that extends AstroNvim and imports packages from the AstroNvim Community.

      "},{"location":"configuration/astronvim/#prerequisits","title":"Prerequisits","text":"
      • Nerd Fonts version 3 - download a full font or only the symbols
      • fzf fuzzy finder (ubuntu archive)
      • gtu (Ubuntu package archive)
      • btm from GitHub repository releases
      • node.js (version 20) for Mason install of many LSP servers

      AstroNvim requires node.js

      AstroNvim uses Mason to install LSP servers, format and lint tools. Many LSP servers require node.js to install and function.

      Node.js install - Practicalli Engineering Playbook

      Kitty Terminal with Nerd Fonts

      Kitty Terminal - Practicalli Engineering Playbook provides examples of using Nerd Fonts or Nerd Font symbols with the Kitty terminal.

      "},{"location":"configuration/astronvim/#clone-astronvim","title":"Clone AstroNvim","text":"

      Clone AstroNvim repository to $HOME/.config/astronvim/

      git clone --depth 1 https://github.com/AstroNvim/AstroNvim ~/.config/astronvim\n

      $HOME/.config/nvim can be used instead if only ever using one configuration for Neovim.

      "},{"location":"configuration/astronvim/#clone-astronvim-user-config","title":"Clone AstroNvim user config","text":"

      AstroNvim provides a template repository to create a user configuration. The template includes AstroNvim Community configuration to make it easier to extend the feature of AstroNvim.

      Practicalli AstroNvim Config is a clone of the AstroNvim user config with additional configuration to support Clojure development.

      Practicalli AstroNvim ConfigAstroNvim User Config

      Clone the Practicalli AstroNvim config which provides a user configuration with Clojure support

      git clone http://github.com/practicalli/astronvim-config $HOME/.config/astronvim/lua/user\n

      Or clone to a separate directory and create a symbolic link

      git clone http://github.com/practicalli/astronvim-config $HOME/.config/astronvim-config && \\\nln -s $HOME/.config/astronvim-config/ $HOME/.config/astronvim/lua/user\n

      Create your own user configuration using the AstroNvim user configuration template repository.

      Create a repository from the AstroNvim/user_example repository template

      Clone the newly created repository into the existing AstroNvim configuration, in a user directory

      git clone git@github.com/<github-account>/<new-repository> $HOME/.config/astronvim/lua/user\n

      "},{"location":"configuration/astronvim/#configure-shell-alias","title":"Configure shell alias","text":"

      Create a shell alias that sets NVIM_APPNAME to the location of the AstroNvim community config

      Add alias to .bashrc for Bash shell or .zshenv for Zsh

      alias astro=\"NVIM_APPNAME=astronvim nvim\"\n

      Configure shell alias

      "},{"location":"configuration/astronvim/#post-install","title":"Post install","text":"

      Open a terminal and use the astro alias to run Neovim.

      astro\n

      NVIM_APPNAME=astronvim nvim to run Neovim with astronvim without setting a shell alias.

      Neovim will open and display the Lazy plugin manager UI, showing the progress of plugin installation. This should only happen on the first run.

      Unattended post install

      Plugins can be installed without running the Neovim editor UI

      nvim --headless -c 'autocmd User LazyDone quitall'\n
      "},{"location":"configuration/astronvim/#check-health","title":"Check Health","text":"

      Run the Neovim :checkhealth command to report on the general Neovim install and supporting tools

      "},{"location":"configuration/astronvim/#add-lsp-dap-lint-and-format-tools","title":"Add LSP DAP Lint and Format tools","text":"

      SPC p m to launch Mason which manages LSP servers, linters, filters ...

      "},{"location":"configuration/astronvim/#configure-format-rules","title":"Configure format rules","text":"

      The configuration files for each lint and format tool should be used by Neovim.

      Setting a different location for these files has proved challenging. plugin/null-ls.lua has a section to override its builtin configuration for each lint and format tool, however, in tests Practicalli was unable to succeffuly set a different location.

      "},{"location":"configuration/astronvim/config-design/","title":"\ud83d\udce6 Practicalli AstroNvim Config Design","text":"

      A guide to the AstroNvim Config user configuration created by Practicalli to support Clojure development.

      AstroCommunity used where possible

      Plugins and configuration is added vial AstroCommunity were possible, to minimise the code size and maintenance of the configuration

      "},{"location":"configuration/astronvim/config-design/#user-config-overview","title":"User Config overview","text":"

      core.lua is for tuning plugins shipped with astronvim config

      plugins/ for additional plugins organised logically. All .lua files are read from this directory

      • user.lua for general user defined plugins
      • clojure.lua adds Conjure and parinf, ensuring Clojure treesitter parser and Clojure LSP
      "},{"location":"configuration/astronvim/config-design/#clojure-support","title":"Clojure support","text":"

      The AstroCommunity provides a Clojure language pack that adds Conjure and nvim-parinfer, along with clojure Treesitter parser and clojure-lsp support.

      AstroCommunity PackManually add plugins

      Edit the plugins/community.lua file and import the Clojure pack. The \"AstroNvim/astrocommunity\", repository is already added to to the file.

      -- Packs\n-- Treesitter: clojure , Lsp: clojure-lsp, Lint/format:\n{ import = \"astrocommunity.pack.clojure\" },\n
      Override AstroCommunity Pack

      Create a plugins/clojure.lua file and add the AstroCommunity repository, Clojure pack and additional configuration to your own preferences

      Clojure configuration with user configration overrides

      return {\n  \"AstroNvim/astrocommunity\",\n  { import = \"astrocommunity.pack.clojure\" },\n  {\n    \"Olical/conjure\",\n    -- load plugin on filetypes\n    ft = { \"clojure\", \"fennel\" },\n    config = function()\n      -- HUD\n      -- Example: Set to `\"SE\"` and HUD width to `1.0` for full width HUD at bottom of screen\n      vim.g[\"conjure#log#hud#width\"] = 1 -- Width of HUD as percentage of the editor width, 0.0 and 1.0.\n      vim.g[\"conjure#log#hud#enabled\"] = false -- Display HUD\n      vim.g[\"conjure#log#hud#anchor\"] = \"SE\" -- Preferred corner position for the HUD\n      vim.g[\"conjure#log#botright\"] = true -- Open log at bottom or far right of editor\n      -- REPL\n      vim.g[\"conjure#extract#context_header_lines\"] = 100 -- Number of lines to check for `ns` form\n      vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#enabled\"] = false -- ;; Start \"auto-repl\" process, eg. babashka\n      vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#hidden\"] = true -- ;; Hide auto-repl buffer when triggered\n      vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#cmd\"] = nil -- ;; Command to start the auto-repl\n      -- ;; Automatically require namespace of new buffer or current buffer after connection\n      vim.g[\"conjure#client#clojure#nrepl#eval#auto_require\"] = false\n      -- Reloading code\n      -- Function to call on refresh (reloading) the log, namespace-qualified name of a zero-arity\n      -- vim.g[\"conjure#client#clojure#nrepl#refresh#after\"] = nil\n      -- The namespace-qualified name of a zero-arity function to call before reloading.\n      -- vim.g[\"conjure#client#clojure#nrepl#refresh#before\"] = nil\n      -- List of directories to scan. If no directories given, defaults to all directories on the classpath.\n      -- vim.g[\"conjure#client#clojure#nrepl#refresh#dirs\"] = nil\n      -- Testing\n      -- ;; Test runner called from the test key mappings\n      vim.g[\"conjure#client#clojure#nrepl#test#runner\"] = \"kaocha\"\n      -- Print raw test evaluation result, suppressing prefix for stdout lines `; (out)`\n      -- vim.g[\"conjure#client#clojure#nrepl#test#raw_out\"] = nil\n      -- Override string appended to the end of the test runner calls\n      -- vim.g[\"conjure#client#clojure#nrepl#test#call_suffix\"] = nil\n    end\n  },\n  {\n    \"gpanders/nvim-parinfer\",\n    ft = lisp_dialects,\n    config = function()\n      vim.g.parinfer_force_balance = true\n      vim.g.parinfer_comment_chars = \";;\"\n    end,\n  },\n}\n

      Add Conjure and parinfer plugin that will load when Clojure or Fennel file is opened.

      Clojure Packages in AstroNvim user configuration

      ```lua title=\".config/astronvim-config/plugins/clojure.lua\"\n-- Lazy Package manager configuration\nreturn {\n  {\n    \"Olical/conjure\",\n    -- load plugin on filetypes\n    ft = { \"clojure\", \"fennel\" },\n  },\n\n  {\n    \"gpanders/nvim-parinfer\",\n    ft = { \"clojure\", \"fennel\" },\n    config = function()\n      vim.g.parinfer_force_balance = true\n      vim.g.parinfer_comment_chars = \";;\"\n    end,\n  },\n}\n```\n

      Improve syntax highlighting by installing the Clojure parser for Treesitter.

      Treesitter Parser for clojure in AstroNvim user configuration

      .config/astronvim-config/plugins/treesitter.lua
      return {\n  \"nvim-treesitter/nvim-treesitter\",\n  opts = function(_, opts)\n    -- add more things to the ensure_installed table protecting against community packs modifying it\n    opts.ensure_installed = require(\"astronvim.utils\").list_insert_unique(opts.ensure_installed, {\n      -- \"lua\"\n    \"clojure\"\n    })\n  end,\n}\n

      Install Treesitter Clojure Parser manually

      :TSInstall clojure in Neovim will install the parser. A parser not included in the opts.ensure_installed configuration must be updated manually each time treesitter plugin is updated

      "},{"location":"configuration/astronvim/config-design/#clojure-mappings","title":"Clojure Mappings","text":"

      Conjure mappings are defined respective to a <localleader> value. Define a local leader in the AstroNvim user configuration, e.g. , and all Conjure mappings become available.

      AstroNvim 3.17.0 has localleader

      AstroNvim 3.17.0 release sets localleader to , so a separate setting is not required in the user configuration (unless a different localleader is preferred)

      Set localleader in user config

      options.lua in the user configuration provides a consistent way to set Neovim options.

      .config/astronvim-config/options.lua
      -- set vim options here (vim.<first_key>.<second_key> = value)\nreturn {\n  opt = {\n    -- set to true or false etc.\n    relativenumber = true, -- sets vim.opt.relativenumber\n    number = true,         -- sets vim.opt.number\n    spell = false,         -- sets vim.opt.spell\n    signcolumn = \"auto\",   -- sets vim.opt.signcolumn to auto\n    wrap = false,          -- sets vim.opt.wrap\n  },\n  g = {\n    mapleader = \" \",                 -- sets vim.g.mapleader\n    maplocalleader = \",\",            -- Set local leader key binding (supports Conjure key bindings)\n    autoformat_enabled = true,       -- enable or disable auto formatting at start (lsp.formatting.format_on_save must be enabled)\n    cmp_enabled = true,              -- enable completion at start\n    autopairs_enabled = true,        -- enable autopairs at start\n    diagnostics_mode = 3,            -- set the visibility of diagnostics in the UI (0=off, 1=only show in status line, 2=virtual text off, 3=all on)\n    icons_enabled = true,            -- disable icons in the UI (disable if no nerd font is available, requires :PackerSync after changing)\n    ui_notifications_enabled = true, -- disable notifications when toggling UI elements\n    VM_leader = \"gm\"                 -- Visual Multi Leader (multiple cursors)\n  },\n}\n
      "},{"location":"configuration/astronvim/config-design/#clojure-lsp","title":"Clojure LSP","text":"

      Clojure LSP support is enabled via the AstroCommunity Clojure pack.

      clojure_lsp can be added using Mason UI, SPC p m or in the plugins/mason.lua file

      Manual user config of clojure lsp server
      -- customize mason plugins\nreturn {\n  -- use mason-lspconfig to configure LSP installations\n  {\n    \"williamboman/mason-lspconfig.nvim\",\n    -- overrides `require(\"mason-lspconfig\").setup(...)`\n    opts = function(_, opts)\n      -- add more things to the ensure_installed table protecting against community packs modifying it\n      opts.ensure_installed = require(\"astronvim.utils\").list_insert_unique(opts.ensure_installed, {\n        -- \"clojure_lsp\",  -- provide by Clojure pack\n        \"marksman\", -- Markdown structure (also in markdown pack)\n        \"yamlls\",\n      })\n    end,\n  },\n}\n
      "},{"location":"configuration/astronvim/config-design/#snippets","title":"Snippets","text":"

      The AstroNvim user example includes a commented LuaSnip configuration

      .config/astronvim-config/plugins/core.lua
        -- {\n  --   \"L3MON4D3/LuaSnip\",\n  --   config = function(plugin, opts)\n  --     require \"plugins.configs.luasnip\" (plugin, opts)  -- include the default astronvim config that calls the setup call\n  --     -- add more custom luasnip configuration such as filetype extend or custom snippets\n  --     local luasnip = require \"luasnip\"\n  --     luasnip.filetype_extend(\"javascript\", { \"javascriptreact\" })\n  --   end,\n  -- },\n

      AstroNvim includes a Recipe for custom snippets

      return {\n  plugins = {\n    {\n      \"L3MON4D3/LuaSnip\",\n      config = function(plugin, opts)\n        require \"plugins.configs.luasnip\"(plugin, opts) -- include the default astronvim config that calls the setup call\n        require(\"luasnip.loaders.from_vscode\").lazy_load { paths = { \"./lua/user/snippets\" } } -- load snippets paths\n      end,\n    },\n  },\n}\n

      Practicalli AstroNvim Config combines the two examples to get

      AstroNvim config with custom VS Code style snippets

      .config/astronvim-config/plugins/core.lua
      {\n  \"L3MON4D3/LuaSnip\",\n  config = function(plugin, opts)\n    require \"plugins.configs.luasnip\" (plugin, opts) -- include the default astronvim config that calls the setup call\n    -- add more custom luasnip configuration such as filetype extend or custom snippets\n    require(\"luasnip.loaders.from_vscode\").lazy_load { paths = { \"./lua/user/snippets\" } } -- load snippets paths\n    local luasnip = require \"luasnip\"\n    luasnip.filetype_extend(\"javascript\", { \"javascriptreact\" })\nend,\n},\n
      "},{"location":"configuration/astronvim/config-design/#astronvim-community-packages","title":"AstroNvim Community packages","text":"

      AstroNvim Community provides a large number of packages currated by the community.

      Visit the AstroNvim Community repository on GitHub and browse the packages available.

      import each package of interest to the plugins/community.lua file in the AstroNvim user configuration.

      AstroNvim Community Packages in AstroNvim user configuration

      .config/astronvim-config/plugins/community.lua
      return {\n  -- Add the community repository of plugin specifications\n  \"AstroNvim/astrocommunity\",\n  -- Import each plugin from the Astro Community as required\n  { import = \"astrocommunity.editing-support.todo-comments\" },\n  { import = \"astrocommunity.git.neogit\" },\n  { import = \"astrocommunity.git.octo\" },\n  { import = \"astrocommunity.git.openingh\" },\n}\n

      AstroCommunity packs set up support for each language

      Language packs enabled in Practicalli AstroNvim Config

      .config/astronvim-config/plugin/community.lua
        -- Packs\n  -- Treesitter: dockerfile , Lsp: dockerls & docker_compose_language_service, Lint/format: hadolint\n  { import = \"astrocommunity.pack.docker\" },\n  -- Treesitter: json & jsonc, Lsp: jsonls, Lint/format: stylua\n  { import = \"astrocommunity.pack.json\" },\n  -- Treesitter: lua, Lsp: lua_ls, Lint/format: stylua\n  { import = \"astrocommunity.pack.lua\" },\n  -- Treesitter: markdown & markdown_inline, Lsp: marksman, Lint/format: prettierd\n  -- Pack disabled as prettierd too agressive with format\n  -- { import = \"astrocommunity.pack.markdown\" },\n  -- Treesitter: markdown & markdown_inline, Lsp: marksman, Lint/format: prettierd\n  { import = \"astrocommunity.pack.yaml\" },\n
      "},{"location":"configuration/astronvim/config-design/#themes","title":"Themes","text":"

      Themes are a collection of one or more colorschemes to affect the apperance of text, icons, highlights, etc.

      Themes supporting vim.opt.background can change between dark and light colorscheme (SPC u b UI > background in AstroNvim)

      SPC f t selector shows themes colorschemes, as long as the themes are configured to disable lazy loading

      The default astrodark theme is set via the colorscheme option in init.lua

      Everforest provides a good dark and light theme and supports the background option to toggle between each colorscheme.

      Practicalli AstroNvim Config - default theme

      colorscheme = \"everforest\",\n

      AstroCommunity themes

      Practicalli AstroNvim Config themes

      return {\n{\n\"AstroNvim/astrotheme\", -- default AstroNvim theme\nlazy = false,\n},\n  -- Add the community repository of plugin specifications\n  \"AstroNvim/astrocommunity\",\n  { import = \"astrocommunity.colorscheme.everforest\" },\n  {\n    \"sainnhe/everforest\",\n    lazy = false,\n  },\n  { import = \"astrocommunity.colorscheme.nightfox-nvim\" },\n  {\n    \"EdenEast/nightfox.nvim\",\n    lazy = false,\n  },\n  { import = \"astrocommunity.colorscheme.kanagawa-nvim\" },\n  {\n    \"rebelot/kanagawa.nvim\",\n    lazy = false,\n  },\n  { import = \"astrocommunity.colorscheme.github-nvim-theme\" }, -- no background support\n  {\n    \"projekt0n/github-nvim-theme\",\n    lazy = false,\n  },\n
      "},{"location":"configuration/astronvim/config-design/#configure-lazy-plugins","title":"Configure Lazy plugins","text":"

      Lazy.nvim Plugin specification

      "},{"location":"configuration/astronvim/config-design/#config-format-and-lint-tools","title":"Config Format and Lint tools","text":"

      Disable format on save when tools provide unexpected results

      SPC u f toggles if the respective format tool should run for the current buffer. SPC u F for all buffers of the current kind.

      init.lua lsp section can enable or disable format on save for specific file types.

      Mason is responsible for installing lint and format tools

      null-ls is responsible for running each tool and provides default configuration for code_actions, completion, diagnostics, formatting and hover.

      null-ls built-in configuration

      Override config file unconsistent

      The configuration file defined by -config-path does not always seem to be used when running astronvim. Quit and start Neovim again seems to use the configuration file.

      Override null-ls builtin configuration

      Specify configuration files to use that override the null-ls builtin configuration

      return {\n  \"jose-elias-alvarez/null-ls.nvim\",\n  opts = function(_, config)\n    -- config variable is the default configuration table for the setup function call\n    local null_ls = require \"null-ls\"\n    config.sources = {\n      null_ls.builtins.formatting.markdownlint.with {\n        -- pass arguments to modify/override the null-ls builtin configuration\n        extra_args = { \n          \"--config-path\", \n          vim.fn.expand(\"~/.config/astro-config/tool-config/markdownlint.yaml\") },\n      },\n    }\n    return config -- return final config table\n  end,\n}\n

      vim.fn.expand() reports luacheck error accessing undefined variable but seems to work regardless

      General configuration for LSP Servers .config/astronvim-config/init.lua
        lsp = {\n    -- customize lsp formatting options\n    formatting = {\n      -- control auto formatting on save\n      format_on_save = {\n        enabled = true,     -- format on save globally\n        allow_filetypes = { -- format on save for specified filetypes only\n          -- \"go\",\n        },\n        ignore_filetypes = { -- turn off format on save for specified filetypes\n          -- \"python\",\n        },\n      },\n      disabled = { -- switch off formatting capabilities for the listed language servers\n        -- turn off lua_ls formatting capability if you want to use StyLua to format your lua code\n        -- \"lua_ls\",\n        \"markdownlint\",\n      },\n      timeout_ms = 1000, -- default format timeout\n      -- filter = function(client) -- fully override the default formatting function\n      --   return true\n      -- end\n    },\n    -- enable servers that you already have installed without mason\n    servers = {\n      -- \"pyright\"\n    },\n  },\n
      "},{"location":"configuration/astronvim/config-design/#override-key-binding","title":"Override Key binding","text":"

      AstroNvim uses Lazy package manager to set keys for packages.

      Astrocommunity configuration defines a keys table that is used by Lazy.

      In the user configuration, return a function that sets key bindings to overide the keys table provided by astrocommunity

      Override Key bindings for vim highlighter .config/astronvim-config/plugins/community.lua
      {\n    \"vim-highlighter\",\n    keys = function() \n        return {\n            { \"<leader>nn\", \"<cmd>Hi><CR>\", desc = \"Next Recently Set Highlight\" },\n            { \"<leader>ng\", \"<cmd>Hi<<CR>\", desc = \"Previous Recently Set Highlight\" },\n            { \"<leader>n[\", \"<cmd>Hi{<CR>\", desc = \"Next Nearest Highlight\" },\n            { \"<leader>n]\", \"<cmd>Hi}<CR>\", desc = \"Previous Nearest Highlight\" },\n        }\n    end,\n}\n
      "},{"location":"configuration/astronvim/config-design/#plugin-key-binding","title":"Plugin Key binding","text":"

      Add key binding if a plugin is available wrapped in an if statement, when defining keys in a different place to adding the plugin, e.g whichkey mappings.lua

      if is_available \"plugin-name\" then\n  ,,,\nelse\n
      "},{"location":"configuration/practicalli/","title":"Neovim Config Redux","text":"

      practicalli/neovim-config-redux

      practicalli/neovim-config-redux is a Fennel based configuraion with a wide range of plugins and telescope extensions.

      Clone practicalli/neovim-config-redux or create a fork if intending to customise that configuration

      Multiple Neovim ConfigsSingle Neovim Configs
      git clone https://github.com/practicalli/neovim-config-redux.git ~/.config/neovim-config-redux\n
      git clone https://github.com/practicalli/neovim-config-redux.git ~/.config/nvim\n
      "},{"location":"configuration/practicalli/#screenshots","title":"Screenshots","text":"

      Dashboard using the startup plugin

      Mnemonic menu with which-key

      Telescope buffer selection

      Neogit Git client with diffview panel

      "},{"location":"configuration/practicalli/config-design/","title":"Config Design","text":"

      The overall design of the Practicalli Neovim Config Redux

      "},{"location":"configuration/practicalli/config-design/#initlua","title":"init.lua","text":"
      • bootstrap the aniseed package which compiles the Fennel configuration into Lua, which is then read by Neovim. Defines the entry point to the Fennel configuration as fnl/config/init.fnl
      • installs packer.nvim for package management
      • examples to disable language providers if programming language support is not required (node, perl, python3, ruby)
      "},{"location":"configuration/practicalli/config-design/#fnlconfiginitfnl","title":"fnl/config/init.fnl","text":"
      • load plugin configuration namespace config.plugin
      • load config.util namespace to streamline key binding definitions
      • set the leader key as space and local-leader as ,
      • define key bindings - uses config.util (mapping to be moved to their own namespace)
      • set global Neovim options
      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginfnl","title":"fnl/config/plugin.fnl","text":"

      Define plugins to add functionality to Neovim.

      use is a private function that searches the plugin configuration map for the keyword :mod and loads the associated namespace (namespace defined with a keyword with the same name)

      e.g. in the telescope plugin configuration :mod has a value of :telescope which will load the file fnl/config/plugin/telescope.fnl

        :nvim-telescope/telescope.nvim\n  {:requires [:nvim-lua/popup.nvim\n              :nvim-lua/plenary.nvim]\n   :mod :telescope}\n

      Packer downloads the nvim-telescope/telescope.nvim plugin and all the plugins in :requires section and search for the namespace telescope in file located in the following path fnl/config/plugin/telescope

      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginconjurefnl","title":"fnl/config/plugin/conjure.fnl","text":"

      The majority of default configuration settings are used for Conjure, with the exception of a few commonly used key bindings from Emacs CIDER & Spacemacs. The Heads Up Display (HUD) is also configured to be less intrusive, relying on mostly on inline results.

      Include the conjure and aniseed namespaces

      (module config.plugin.conjure\n  {autoload {nvim aniseed.nvim}})\n

      Configure keybindings to be closer to Spacemacs

      ;; Set e register for evaluation result\n(set nvim.g.conjure#eval#result_register :e)\n\n;; Evaluate root form (top level form) under the cursor\n;; Default: `\"er\"`\n(set nvim.g.conjure#mapping#eval_root_form \"ef\")\n\n;; Evaluate root form under the cursor & insert result as comment\n;; Default: `\"ecr\"`\n(set nvim.g.conjure#mapping#eval_comment_root_form \"e;\")\n\n;; Evaluate file loaded from disk\n;; Default: `\"ef\"`\n(set nvim.g.conjure#mapping#eval_file \"el\")\n

      Configure the HUD to be less intrusive.

      ;; Width of HUD as percentage of the editor width\n;; A float between 0.0 and 1.0.\n;; Default: `0.42`\n(set nvim.g.conjure#log#hud#width 1)\n\n;; Display HUD\n;; Default: `true`\n(set nvim.g.conjure#log#hud#enabled false)\n\n;; Preferred corner position for the HUD, over-ridden by HUD cursor detection\n;; Example: Set to `\"SE\"` and HUD width to `1.0` for full width HUD at bottom of screen\n;; Default: `\"NE\"`\n(set nvim.g.conjure#log#hud#anchor \"SE\")\n\n;; Open log at bottom or far right of editor, using full width or height\n;; Default: `false`\n(set nvim.g.conjure#log#botright true)\n

      Practicalli encourages header comments at the start of each file to describe the purpose of the namespace, so the Clojure ns lookup is extended

      ;; Number of lines to check for `ns` form, used for setting evaluation context\n;; `b:conjure#context` to override a specific buffer that isn't finding the context\n;; Default: `24`\n(set nvim.g.conjure#extract#context_header_lines 100)\n

      Disable the auto-repl as practicalli prefers manage repl connections themselves

      ;; Start \"auto-repl\" process, eg. babashka\n;; when Conjure unable to find candidate REPL process via to an existing nREPL connection\n;; Default: `true`\n(set nvim.g.conjure#client#clojure#nrepl#connection#auto_repl#enabled false)\n\n;; Hide auto-repl buffer when triggered, to avoid the need to interact with that buffer\n;; Default: `false`\n(set nvim.g.conjure#client#clojure#nrepl#connection#auto_repl#hidden true)\n\n;; Command to start the auto-repl\n;; Default: `\"bb nrepl-server localhost:8794\"`\n(set nvim.g.conjure#client#clojure#nrepl#connection#auto_repl#cmd nil)\n\n;; Print raw evaluation result, suppressing prefix for stdout lines `; (out)`\n;; Default: `false`\n(set nvim.g.conjure#client#clojure#nrepl#eval#raw_out true)\n\n;; Automatically require namespace of new buffer or current buffer after connection\n;; Ensures buffers are loaded, required code to compile and (re)loadable.\n;; Default: `true`\n(set nvim.g.conjure#client#clojure#nrepl#eval#auto_require false)\n

      Use lambdaisland/kaocha as the test runner rather, which has a fail fast feature which can be more effective when adding or changing functionality

      ;; Test runner called from the test key mappings\n;; Default: `\"clojure\"`\n(set nvim.g.conjure#client#clojure#nrepl#test#runner \"kaocha\")\n\n;; Print raw test evaluation result, suppressing prefix for stdout lines `; (out)`\n;; Default: `true`\n(set nvim.g.conjure#client#clojure#nrepl#test#raw_out true)\n
      "},{"location":"configuration/practicalli/config-design/#fnlconfigplugintelescopefnl","title":"fnl/config/plugin/telescope.fnl","text":"

      Settings like ignore node_modules and everything in .gitignore to be listed in the file finder.

      Defines a ripgrep command to set parameters for searching files

      Add --hidden to see all dotfiles (regardless of .gitignore patterns)

      Keymaps:

      • <leader>ff open the find files
      • <leader>fg open the fuzzy finder
      • <leader>fb open the find open buffer
      • <leader>fh open the nvim help fuzzy finder
      "},{"location":"configuration/practicalli/config-design/#fnlconfigplugintreesitterfnl","title":"fnl/config/plugin/treesitter.fnl","text":"

      Defines which language parsers and modules to use.

      • automatically use clojure, fennel and markdown parsers (and compile on first run of Neovim)
      • automatically update language parsers when nvim-treesitter plugin updated
      • enable highlight module
      • enable indent module
      (treesitter.setup\n  {:ensure_installed [\"clojure\" \"fennel\" \"markdown\"]\n   :sync_install true\n   :highlight {:enable true}\n   :indent    {:enable true}})\n
      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginlspconfigfnl","title":"fnl/config/plugin/lspconfig.fnl","text":"

      Language Server Protocol for static analysis of code, to provide common formatting, linting and refactoring tooling across all programming languages.

      Define which symbols to show for lsp diagnostics

      (defn define-signs\n  [prefix]\n  (let [error (.. prefix \"SignError\")\n        warn  (.. prefix \"SignWarn\")\n        info  (.. prefix \"SignInfo\")\n        hint  (.. prefix \"SignHint\")]\n  (vim.fn.sign_define error {:text \"\uf057\" :texthl error})\n  (vim.fn.sign_define warn  {:text \"\uf071\" :texthl warn})\n  (vim.fn.sign_define info  {:text \"\uf05a\" :texthl info})\n  (vim.fn.sign_define hint  {:text \"\uf059\" :texthl hint})))\n
      • features and server settings to enable/customize.
      • Handler defines features and how we want to render the server outputs.
      • Capabilities we link with our autocompletion plugin (nvim-cmp), to say to the lsp servers that we have this feature enabled.
      • On_Attach we customize our interaction with the LSP server, here we define the following keymaps:
      • configure all settings above in clojure-lsp server instance.
      "},{"location":"configuration/practicalli/config-design/#fnlconfigplugincmpfnl","title":"fnl/config/plugin/cmp.fnl","text":"

      Configure sources to show in the autocomple menu (i.e. conjure, lsp, buffer) and key bindings to navigate the autocomplete popup menu.

      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginthemefnl","title":"fnl/config/plugin/theme.fnl","text":"

      Add the Neovim GitHub theme which gives 3 dark and 3 light themes to choose from. Individual colors and styles can be configured to change specific parts of the theme.

      The light theme is used by default, with a custom softer background colour that is slightly red-shifted.

      Options are specified in the theme.setup function, where the option names are keywords and the values are strings, boolean or hash-map of more option keywords and values.

      (theme.setup {:theme_style \"light\"\n              :colors {:bg \"#f8f2e6\"}\n              :comment_style \"italic\"})\n

      The colors (Hex values) for each theme are in the github-nvim-theme/lua/github-theme/palette with the overal theme definition in github-nvim-theme/lua/github-theme/theme.lua

      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginsexpfnl","title":"fnl/config/plugin/sexp.fnl","text":"

      Settings for vim-sexp like enabling it for another lisp languages like Fennel and Jannet

      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginlualinefnl","title":"fnl/config/plugin/lualine.fnl","text":"

      Configure the status line (lualine) that shows at the bottom of Neovim, defining colors and elements that appear on that line.

      The Neovim GitHub theme includes definitions to set the look of the status line.

      "},{"location":"configuration/practicalli/packer/","title":"Package Manager","text":"

      Packer is a use-package inspired package management for Neovim.

      Packer is used as the package manager in this guide as it is built on native Neovim packages and supports Luarocks dependencies, use the :help packages command in Neovim for more details.

      Packer is written in Lua and is installed via the init.lua configuration file, although Practicalli Neovim configuration uses Fennel to configure each package added by Packer.

      "},{"location":"configuration/practicalli/packer/#install","title":"Install","text":"

      init.lua is the entry point to the configuration and is the only part that is written in Lua language.

      The configuration bootstraps the Packer package manager and installs the Aniseed compiler required to process the fennel configuration.

      Aniseed compiles and loads fnl/config/init.fnl and all the required namespaces in that file.

      Packer will process the use form in fnl/config/plugin.fnl and install all the packages defined in that form, along with any package specific configuration defined in that package {:mod :namespace-name} file.

      local execute = vim.api.nvim_command\nlocal fn = vim.fn\n\nlocal pack_path = fn.stdpath(\"data\") .. \"/site/pack\"\nlocal fmt = string.format\n\nfunction ensure (user, repo)\n  -- Ensures a given github.com/USER/REPO is cloned in the pack/packer/start directory.\n  local install_path = fmt(\"%s/packer/start/%s\", pack_path, repo, repo)\n  if fn.empty(fn.glob(install_path)) > 0 then\n    execute(fmt(\"!git clone https://github.com/%s/%s %s\", user, repo, install_path))\n    execute(fmt(\"packadd %s\", repo))\n  end\nend\n\n-- Bootstrap essential plugins required for installing and loading the rest.\nensure(\"wbthomason\", \"packer.nvim\")\nensure(\"Olical\", \"aniseed\")\n\n-- Enable Aniseed's automatic compilation and loading of Fennel source code.\nvim.g[\"aniseed#env\"] = {\n  module = \"config.init\",\n  compile = true\n}\n
      "},{"location":"configuration/practicalli/packer/#packages","title":"Packages","text":"

      Neovim packages add extra functionality to Neovim, e.g. conjure package provides an excellent Clojure REPL experience (and supports several other languages too).

      See the packages section for details of the packages used and a breakdown of their configuration.

      "},{"location":"configuration/practicalli/packages/","title":"Add Neovim Packages","text":"

      Evolving Packages in Practicalli config

      Check the practicalli/neovim-config-reduct configuration. Many packages have been added to the configuration and fnl/config/package.fnl is the most up to date list of packages currently used.

      List of packages and their purpose

      Package Description conjure Clojure REPL Driven Development (and other language REPLs) sexp Structured Editing newpaper theme Clean and simple UI & colour scheme, aimed at readably lualine Fast and configurable statusline nvim-treesitter Parse code highly efficiently, client for LSP servers telescope Completion tool, e.g. select files, buffers tabs, packages, etc nvim-tree Visual file manager - open, create, delete, etc. files & directories neogit Magit style visual Git client Octo Git Issues and Pull Requests gitsigns Show diff changes in buffer gutter and status line

      Any specific package configuration & key bindings (on sub page if significant content)

      "},{"location":"configuration/practicalli/packages/#package-selection-criteria","title":"Package selection criteria","text":"

      Packages are more likely to be adopted if:

      • provide valuable (or fun) features
      • work reliably, without generating errors
      • do not conflict with other valuable packages in this configuration
      • written in fennel or lua to aid maintenance
      • provides a setup or config function for setting package options
      • use features provided by Neovim (e.g treesitter)
      • are well documented
      • are easy to configure
      • are easy to use
      • work well with themes (where relevant)
      "},{"location":"configuration/practicalli/packages/#package-updates","title":"Package Updates","text":"

      This Week In Neovim - community update

      "},{"location":"configuration/practicalli/packages/lualine/","title":"Lualine - modeline theme","text":"

      nvim-lualine/lualine.nvim is a fast and configurable statusline for neovim

      Example status line: evil_lualine

      "},{"location":"configuration/practicalli/packages/lualine/#lualine-configuration-in-fennel","title":"Lualine configuration in Fennel","text":"

      nvim/fnl/config/plugin/lualine.fnl

      (module config.plugin.lualine\n  {autoload {core aniseed.core\n             lualine lualine\n             lsp config.plugin.lspconfig}})\n\n(defn lsp_connection []\n  (if (vim.tbl_isempty (vim.lsp.buf_get_clients 0)) \"\uf096\" \"\uf0c8\"))\n\n(def github-lua-theme\n  (core.assoc\n    (require :lualine.themes.auto)\n    :inactive {:a {:bg \"#19181e\" :fg \"#a4a3a6\"}\n               :b {:bg \"#19181e\" :fg \"#a4a3a6\"}\n               :c {:bg \"#19181e\" :fg \"#a4a3a6\"}}\n    :normal {:a {:bg \"#131217\" :fg \"#24292e\"}\n             :b {:bg \"#131217\" :fg \"#3b8eea\"}\n             :c {:bg \"#19181e\" :fg \"#d1d5da\"}}\n    :command {:a {:bg \"#131217\" :fg \"#24292e\"}\n              :b {:bg \"#131217\" :fg \"#ccbed8\"}\n              :c {:bg \"#19181e\" :fg \"#d1d5da\"}}\n    :visual {:a {:bg \"#131217\" :fg \"#24292e\"}\n             :b {:bg \"#131217\" :fg \"#ced4b1\"}\n             :c {:bg \"#19181e\" :fg \"#d1d5da\"}}\n    :replace {:a {:bg \"#131217\" :fg \"#24292e\"}\n              :b {:bg \"#131217\" :fg \"#d1b6bd\"}\n              :c {:bg \"#19181e\" :fg \"#d1d5da\"}}\n    :insert {:a {:bg \"#131217\" :fg \"#24292e\"}\n             :b {:bg \"#131217\" :fg \"#a8d1c9\"}\n             :c {:bg \"#19181e\" :fg \"#d1d5da\"}}))\n\n(lualine.setup\n  {:options {:theme github-lua-theme\n             :icons_enabled true\n             :section_separators [\"\" \"\"]\n             :component_separators [\"\uf44a\" \"\uf438\"]}\n   :sections {:lualine_a []\n              :lualine_b [[:mode {:upper true}]]\n              :lualine_c [[\"FugitiveHead\"]\n                          [:filename {:filestatus true\n                                      :path 1}]]\n              :lualine_x [[:diagnostics {:sections [:error\n                                                    :warn\n                                                    :info\n                                                    :hint]\n                                         :sources [:nvim_lsp]}]\n                          [lsp_connection]\n                          :location\n                          :filetype]\n              :lualine_y [:encoding]\n              :lualine_z []}\n   :inactive_sections {:lualine_a []\n                       :lualine_b []\n                       :lualine_c [[:filename {:filestatus true\n                                               :path 1}]]\n                       :lualine_x []\n                       :lualine_y []\n                       :lualine_z []}})\n
      "},{"location":"configuration/practicalli/packages/nvim-treesitter/","title":"Nvim Treesitter","text":"

      Treesitter provides language specific parsing, highlight and indent features and so is a fundamental plugin to use with Neovim.

      clojure, fennel, markdown and org parsers are automatically installed in the practicalli/neovim-config-redux configuration.

      • :TSInstallInfo lists language parsers and install status
      • :TSUpdate {language} to update a parser to the latest compatible version (specified in nvim-treesitter lockfile.json).
      • :TSInstall {language} compiles and installs a parser for the given language.
      • :TSUpdateSync to update all parsers to the latest available versions
      "},{"location":"configuration/practicalli/packages/nvim-treesitter/#nvim-treesitter-configuration","title":"nvim-treesitter configuration","text":"

      clojure, fennel, markdown and org parsers are automatically installed if not already available.

      :sync_install true automatically updates the parsers when the nvim-treesitter plugin is updated. Treesitter and its parsers are actively developed, so its important to ensure parsers are kept up to date. This is the equivalent of manually running :TSUpdateSync.

      Parser highlight and indent modules are enabled by default

      In fnl/config/plugin/treesitter.fnl

      (module config.plugin.treesitter\n  {autoload {treesitter nvim-treesitter.configs}})\n\n(treesitter.setup\n  {:ensure_installed [\"clojure\" \"fennel\" \"markdown\" \"org\"]\n   :sync_install true\n   :highlight {:enable true}\n   :indent    {:enable true}})\n
      "},{"location":"configuration/practicalli/packages/nvim-treesitter/#manually-install-parsers","title":"Manually Install Parsers","text":"

      nvim-treesitter provides the TSInstall command to generate a parser for a specific language, assuming that language is supported.

      A compiler (gcc, clang, etc) should be installed in the operating system on which nvim is running

      :TSInstall {language}\n

      TAB completion lists the available language parsers, TAB and S-TAB to navigate the auto-completion popup.

      "},{"location":"install/","title":"Install Overview","text":"

      Practicalli Neovim provides a feature rich configuration for Neovim and all the tools required for effective Clojure development (and other Lisp dialects too).

      • Clojure tooling and a Java SDK (Java Virtual Machine)
      • Neovim 0.9.x or nightly build
      • Neovim package manager and packages
      • NerdFonts for icon support in themes and status line

      Neovim 0.9.x latest stable release

      Content and configuration in this book has been tested against Neovim 0.9.x over the summer of 2023

      "},{"location":"install/#install-summary","title":"Install summary","text":"

      If you are familiar with most of the tools required, then the quick start list below provides an ultra-terse version on how to get started with Neovim and Clojure development.

      • Install Neovim 0.9.x or greater
        • Linux AppImage
        • brew install --HEAD neovim for Homebrew install of development version
      • Install supporting tools
        • tar & curl and a C compiler, e.g. gcc for Linux or clang for android/termix (required by nvim-treesitter)
        • ripgrep & fd to search for files (used by telescope)
        • luarocks for LSP support in AstroNvim
      • Clone Neovim Config
      • Run nvim in a terminal and ignore warnings, press RTN
        • SPC P i or :PackerInstall command in Neovim to install packages
      • Install Clojure CLI and supporting tools
      • Clone / fork practicalli/clojure-deps-edn or add an alias with the required config to use nrepl and cider-nrepl
      • Run a Clojure REPL process - in a terminal session with nREPL, e.g. using one of the REPL aliases from practicalli/clojure-deps-edn
        • clojure -M:repl/rebel for a rich REPL UI with auto-completion & docs
        • clojure -M:repl/headless - headless REPL process when working exclusively in a Clojure connected editor
      • Open a Clojure file in Neovim - Conjure will automatically connect
      "},{"location":"install/#next-steps","title":"Next Steps","text":"

      Learn how to use Neovim and how to use Conjure for REPL driven development

      "},{"location":"install/clojure/","title":"Install Clojure","text":"

      A rich Clojure REPL workflow is provided by the Conjure package, which works with Clojure CLI and Leiningen projects, assuming the respective tool is installed.

      Clojure LSP is highly recommended and packages to use an installed clojure-lsp tool are in the practicalli/neovim-config-redux configuration

      "},{"location":"install/clojure/#clojure-cli","title":"Clojure CLI","text":"

      Practicalli Clojure install guide

      Clojure CLI provides a way to run Clojure code, packaged Clojure (jar) and run a Clojure REPL.

      Practicalli Clojure install guide details prerequisites, Clojure install options and supporting tools for an enhanced developer workflow.

      Visit the Clojure Getting Started guide for the Clojure CLI or to check the latest release version.

      Practicalli Clojure CLI Config provides a wide range of community tools that extend the features of Clojure CLI, creating a rich development environment for use across all projects.

      Aliases are required for many examples

      Without Practicalli Clojure CLI Config many commands provided in this book are not available unless similar alias definitions are added to a either a project or user level deps.edn configuration.

      "},{"location":"install/clojure/#language-server-protocol","title":"Language Server Protocol","text":"

      Neovim Treesitter surfaces information from Language Server Protocol (LSP) servers to assist with development and refactor of Clojure code.

      Clojure LSP installation guide shows how to install the Clojure LSP binary for the relevant operating system.

      Once installed, run clojure-lsp -v in a terminal to ensure the command is working.

      practicalli/clojure-lsp-config

      practicalli/clojure-lsp-config provides a complete configuration for clojure-lsp (config.edn), including a wide range of snippets and less restrictive formatting rules (cljfmt.edn)

      clj-kondo provides static analysis of source code files, providing subtle warnings as Clojure code is written to help the developer follow idioms and avoid syntatic errors.

      Clojure LSP includes clj-kondo to provide an implementation of the Language Server Protocol for the Clojure Language.

      Clojure LSP installation guide Treesitter Fennel Configuration

      "},{"location":"install/clojure/#leiningen","title":"Leiningen","text":"

      Many existing Clojure projects use Leiningen build automation tool (although many new projects use Clojure CLI as well or instead of Leiningen).

      The code is the same regardless of tooling choice. The overall workflow is the same, although Clojure CLI may provide more workflow options.

      Follow the install instructions at Leiningen.org if required.

      "},{"location":"install/neovim/","title":"Install Neovim","text":"

      Neovim releases

      Neovim 8 is the minimum version for this configuration and Neovim 0.9.0 is currently being tested.

      Follow the install Neovim guide for the specific operating system.

      "},{"location":"install/neovim/#suppoting-tools","title":"Suppoting Tools","text":"

      Neovim uses several external tools for searching for files, search file contents and using the operating system clipbaord.

      AstroNvim requires node.js

      AstroNvim uses Mason to install LSP servers, format and lint tools. Many LSP servers require node.js to install and function.

      Node.js install - Practicalli Engineering Playbook

      Debian / Ubuntu

      Install the following packages to support Neovim

      • ripgrep fast file contents search (used by telescope)
      • find-fd advanced search tool
      • xclip clipboard as a provider tools for Neovim copy/paste
      • luarocks for LSP servers (AstroNvim)
      sudo apt install find-fd xclip luarocks\n

      Add set clipboard+=unnamedplus to the Neovim configuration to use the Linux clipboard tool

      Wayland requires wl-clipboard

      Install the wl-clipboard package to use the Wayland desktop clipboard with Neovim

      sudo apt install wl-clipboard\n

      "},{"location":"install/neovim/#install-neovim_1","title":"Install Neovim","text":"Linux AppImageUbuntu/DebianBuild from Source

      Download the AppImage from the Neovim Release page and place the file on the executable path, e.g. $HOME/.local/bin

      Make the AppImage executable

      chmod u+x nvim.appimage\n

      Run neovim from the AppImage

      nvim.appimage\n

      Create a symbolic link called nvim to the nvim.appimage

      ln -s $HOME/.local/bin/nvim.appimage $HOME/.local/bin/nvim\n

      Download the Linux AppImage from the Neovim Releases page

      Or build Neovim from source and generate a .deb file from the build.

      Linux version only packaged as AppImage from Neovim 0.9 onward

      Neovim Build Prerequisites for each operating system

      Ubuntu/Debian Packages

      Install packages to support building Neovim

      sudo apt-get install ninja-build gettext cmake unzip curl\n

      Clone the Neovim GitHub repository

      git clone --origin neovim https://github.com/neovim/neovim.git\n
      Change into the cloned directory and change to the stable release to build version 0.9.0

      git checkout stable\n

      Build a release

      make CMAKE_BUILD_TYPE=Release                                                                                                              \u2500\u256f\n

      Once the nvim release has been built, create a debian package for use with Ubuntu and Debian systems

      cpack -G DEB\n
      "},{"location":"install/neovim/#post-install-checks","title":"Post Install checks","text":"

      Ensure supporting tools and binaries are available in the operating system by running the Neovim Heath Check.

      nvim in a terminal to run NeoVim and check the installation is working without error.

      :checkhealth to run a check supporting tools are available to NeoVim.

      A report is generated and shown in NeoVim

      j / k to scroll through the checkhealth report

      Review the warnings and install tooling that is required for languages that will be used.

      Ignore Provider Warnings

      It is safe to ignore language provider warnings.

      Language Providers can be disabled in the Neovim configuration to remove the warnings from :checkhealth report. Examples of disabling language provders are in the practicalli/neovim-config-redux configuration, covered in the Neovim Config install step

      "},{"location":"introduction/community-projects/","title":"Community Configuration Projects","text":"

      Practicalli Neovim book covers the following configurations:

      "},{"location":"introduction/community-projects/#practicalli-neovim-config-redux","title":"Practicalli Neovim Config Redux","text":"

      Practicalli Neovim Config Redux

      • Fennel configuration
      • Packer package manager & Treesitter support
      • Mnemonic key bindings
      • Telescope selectors
      • Autocompletion (cmp) & snippets (luasnip)
      • Esc with center row keys, e.g. \"fd\"
      "},{"location":"introduction/community-projects/#astronvim-and-practicalli-astronvim-config","title":"AstroNvim and Practicalli AstroNvim Config","text":"

      AstroNvim and Practicalli AstroNvim Config organised configuration with a polished UI

      • Neovim 9 support
      • Lazy for plugins (packages for Neovim)
      • Mason to manage install for LSP, DAP, lint and format tools
      • Treesitter and language parser support
      • Telescope selectors
      • Notification dialogs
      • Autocompletion (cmp) & snippets (luasnip)
      • Neovim 9 background switch (live toggle light & dark theme)
      • Hidden command line cmdheight=0 (Neovim 0.8 onward)
      • Esc with center row keys, e.g. \"fd\" (user: plugins/core.lua)
      "},{"location":"introduction/community-projects/#alternative-configurations","title":"Alternative configurations","text":"

      Practicalli Neovim does not cover the following Community configurations.

      • Magit Kit fennel configuration from the author of Conjure
      • cajus-nvim inspiration for practicalli/neovim-config-redux
      • LazyVim lazy & mason configuration
      • NvChad polished UI with Lazy optomisations

      Long term project: Fennel config with AstroNvim-like UI experience

      A very long term goal for Practicalli is to create a Neovim configuration written predominatly in Fennel, providing a rich user experience on par with the very polished experience of AstroNvim.

      Lazy and Mason should be used to manage packages and tools (LSP & DAP servers, lint & format tools).

      Which-key should provide a mnemonic menu system similar to the Spacemacs experience.

      "},{"location":"introduction/contributing/","title":"Contributing to Practicalli","text":"

      Practicalli books are written in markdown and use MkDocs to generate the published website via a GitHub workflow. MkDocs can also run a local server using the make docs target from the Makefile

      By submitting content ideas and corrections you are agreeing they can be used in this book under the Creative Commons Attribution ShareAlike 4.0 International license. Attribution will be detailed via GitHub contributors.

      All content and interaction with any persons or systems must be done so with respect and within the Practicalli Code of Conduct.

      "},{"location":"introduction/contributing/#book-status","title":"Book status","text":""},{"location":"introduction/contributing/#submit-and-issue-or-idea","title":"Submit and issue or idea","text":"

      If something doesnt seem quite right or something is missing from the book, please raise an issue via the GitHub repository explaining in as much detail as you can.

      Raising an issue before creating a pull request will save you and the maintainer time.

      "},{"location":"introduction/contributing/#considering-a-pull-request","title":"Considering a Pull request?","text":"

      Before investing any time in a pull request, please raise an issue explaining the situation. This can save you and the maintainer time and avoid rejected pull requests.

      Please keep pull requests small and focused, as they are much quicker to review and easier to accept. Ideally PR's should be for a specific page or at most a section.

      A PR with a list of changes across different sections will not be merged, it will be reviewed eventually though.

      "},{"location":"introduction/contributing/#thank-you-to-everyone-that-has-contributed","title":"Thank you to everyone that has contributed","text":"

      A huge thank you to Rich Hickey and the team at Cognitect for creating and continually guiding the Clojure language. Special thank you to Alex Miller who has provided excellent advice on working with Clojure and the CLI tooling.

      The Clojure community has been highly supportive of everyone using Clojure and I'd like to thank everyone for the feedback and contributions. I would also like to thank everyone that has joined in with the London Clojurins community, ClojureBridgeLondon, Clojurians Slack community, Clojurians Zulip community and Clojureverse community.

      Thank you to everyone who sponsors the Practicalli websites and videos and for the Clojurists Together sponsorship, it helps me continue the work at a much faster pace.

      Special thanks to Bruce Durling for getting me into Cloure in the first place.

      "},{"location":"introduction/features/","title":"Neovim features","text":"

      A clean UI provides for a distraction free development experience, with only the essential information presented in the Neovim statusline or inline with the code

      • Conjure - automatic Clojure REPL connection, evaluation, test runners
      • Treesitter
      • Plug-in Manager (e.g. Lazy.nvim)
      • LSP - auto-completion, snippets, inline linting, reference navigation, refactor and unit test coverage
      • statusline - LSP status, diff changes, filetype, cursor position
      • Selection narrowing completion of files, packages, colour schemes, etc
      • File browser - telescope selection narrowing and visual file system navigation
      • Version Control gutter indicators for changed lines
      • todo comments todo, fix, notes, indicators with gutter icons
      • relative line numbers for vim-style navigation
      "},{"location":"introduction/features/#conjure","title":"Conjure","text":"

      Conjure An interactive environment for evaluating code, e.g. a Clojure REPL. Conjure automatically connects to an nREPL process running in the current project.

      Evaluate Clojure code as its developed for an instant feedback workflow.

      Run unit tests with Kaocha test runner (Cognitect Labs and ClojureScript runners also available)

      Fireplace has been a long-standing plugin for Vim to support Clojure REPL connection.

      "},{"location":"introduction/features/#lazy-plugin-manager","title":"Lazy Plugin manager","text":"

      Lazy.nvim manages neovim plugins with a rich UI that provides an enjoyable user experience. Plugins are automatically installed during startup and lists the status of each plugins.

      Plugins are automatic cached & bytecode compiled and can be lazy loaded to streamline startup time and resource usage based on events, commands, filetypes, and key mappings. Efficient plugin downlaods using partial blobless clones of plugin repositories, i.e. --filter=blob:none

      Lazy.nvim

      "},{"location":"introduction/features/#treesitter","title":"Treesitter","text":"

      Neovim provides highly effective syntax highlighting of source code due to Treesitter.

      Tree-sitter parses files opened in Neovim and builds a concrete syntax tree that any Neovim plugin can use to efficiently provide feedback. Treesitter uses incremental parsing to efficiently update the syntax tree as a file is edited.

      • parse on every keystroke in a text editor
      • provide useful results even in the presence of syntax errors

      Treesitter

      "},{"location":"introduction/features/#language-server-protocol","title":"Language Server Protocol","text":"

      Neovim includes an LSP client which uses the information recieved from a language specific LSP server in real-time to provide a range of services:

      • auto-completion of function and symbol names
      • live linting as code is typed or opened from a file
      • formatting
      • function signatures and help documentation
      • diagnostics (syntax errors & idioms)
      • symantic analysis providing rename through project, go-to-definition & find-references

      LSP feedback is often presented in the buffer, file browser and status line of Neovim.

      LSP Server implementation is not universal

      LSP is a relatively new specification and many server implmentations are still evolving or are yet to be created.

      Lint tools tend to be more prevelent and may be required in concert with or in the absence of an LSP server.

      LSP related Plugins
      • neovim/nvim-lspconfig - connect Neovim lsp client to lsp servers
      • jose-elias-alvarez/null-ls.nvim - hook format and lint tools into the Neovim LSP client
      • jayp0521/mason-null-ls.nvim - automatically install formatters/linters to be used by null-ls
      • williamboman/mason - install and manage LSP servers, DAP servers, linters, and formatters
      • williamboman/mason-lspconfig - register LSP configs with neovim so LSP client can connect to servers
      "},{"location":"introduction/features/#lint-and-format-tools","title":"Lint and format tools:","text":"

      Linters check code for common problems and provide hints on how to correct any detected issues.

      Format tools suppor code to conforming to a specified coding style, typically these run when save-file is run.

      null-ls provides extensive builtin configuration for programming languages and configuration formats. null-ls also passes lint and format tool information to the Neovim LSP client, extending the range of language support.

      "},{"location":"introduction/features/#selection-narrowing","title":"Selection Narrowing","text":"

      telescope.nvim is a highly extendable fuzzy finder over lists with community driven pickers, sorters and previewers.

      Navigate and narrow lists of files, packages, environment variables, ports, colour schemes (themes) and any other list of items effectively.

      Telescope File browser popup also explores the file system and in Normal mode can be used to create files and directories

      The telescope list narrows matches as characters are typed

      "},{"location":"introduction/features/#version-control","title":"Version Control","text":"

      Gitsigns hightlights buffer changes in the gutter

      Lualine shows number of Git changes in status line

      Diffview to review all changes for any git revision

      Neogit provides a rich git client to add, stash, commit, push & pull changes.

      Octo provides a GitHub specific client to manage issues and pull requests, using GitHub CLI authentication.

      LazyGit UI

      "},{"location":"introduction/features/#file-browser","title":"File Browser","text":"

      neo-tree provides a visual file system explorer that can also create and delete files and directories

      "},{"location":"introduction/features/#todo-comments","title":"TODO Comments","text":"

      Highlight tasks, fixes, notes and dragons comments, including icons in the gutter. Use Telescope to navigate TODO comments in the current project.

      "},{"location":"introduction/features/#status-line","title":"Status Line","text":"

      LSP feedback

      "},{"location":"introduction/features/#markdown","title":"Markdown","text":"
      • LSP server
      • Marksman: select anchors and pages for links
      "},{"location":"introduction/fennel/","title":"Fennel","text":"

      Lua is the defacto language for Neovim plugin development and configuration.

      Fennel can be used to write Neovim packages and configuration, using nfnl to generate the equivalent Lua code that Neovim runs.

      Although Neovim fully supports Vimscript, Practicalli encourages Fennel or Lua, as Vimscript is a niche language with quite complex syntax.

      "},{"location":"introduction/fennel/#overview","title":"Overview","text":"

      Fennel is a programming language that brings together the speed, simplicity, and reach of Lua with the flexibility of a lisp syntax and macro system.

      • Full Lua compatibility: Easily call any Lua function or library from Fennel and vice-versa.
      • Zero overhead: Compiled code should be just as efficient as hand-written Lua.
      • Compile-time macros: Ship compiled code with no runtime dependency on Fennel.
      • Embeddable: Fennel is a one-file library as well as an executable. Embed it in other programs to support runtime extensibility and interactive development.

      Anywhere you can run Lua code, you can run Fennel code.

      Translate Lua to Fennel

      See Fennel is an online antifennel tool to convert Lua to Fennel or Fennel to Lua.

      practicalli/neovim-config-redux configuration provides helper functions to minimise the translation required.

      "},{"location":"introduction/fennel/#fennel-packages","title":"Fennel Packages","text":"

      The Conjure package which provides the Clojure REPL (and much more) is written in Fennel.

      "},{"location":"introduction/fennel/#nfnl","title":"nfnl","text":"

      nfnl generates Lua code from Fennel code. Neovim runs the generated Lua code.

      nfnl loads only when working in directories containing a .nfnl.fnl configuration file, so has zero overhead when not working with fennel.

      *.fnl files are automatically compiled to *.lua when changes are saved, showing any compilation errors to provide an effective feedback loop.

      nfnl standard library

      nfnl plugin example

      "},{"location":"introduction/fennel/#development-tooling","title":"Development tooling","text":"

      Neovim support

      • Anti-fennel - convert from Lua code to Fennel code.
      • nfnl - write plugins or configuration for Neovim with great runtime performance
      • hotpot - seamless Fennel inside Neovim

      See Fennel is an online antifennel tool to convert between Lua and Fennel.

      Guide to plugin development with fennel

      Emacs support:

      • technomancy/fennel-mode and Emacs mirror repository
      "},{"location":"introduction/fennel/#playing-games","title":"Playing Games","text":"

      TIC-80 is a simulated computer environment to to write code, design art, compose music and retro style game games.

      L\u00d6VE is a framework for making games with the Lua programming language, allows import from external resources and can use any resolution or memory resources required.

      TIC-80 and L\u00d6VE provide cross-platform support across Windows, Mac and Linux systems. TIC-80 games can also be played in the browser.

      "},{"location":"introduction/repl-workflow/","title":"REPL Driven Development","text":"

      Always be REPL'ing

      Coding without a REPL feels limiting. The REPL provides fast feedback from code as its crafted, testing assumptions and design choices every step of the journey to a solution - John Stevenson, Practical.li

      Clojure is a powerful, fun and highly productive language for developing applications and services. The clear language design is supported by a powerful development environment known as the REPL (read, evaluate, print, loop). The REPL gives you instant feedback on what your code does and enables you to test either a single expression or run the whole application (including tests).

      REPL driven development is the foundation of working with Clojure effectively

      An effective Clojure workflow begins by running a REPL process. Clojure expressions are written and evaluated immediately to provide instant feedback. The REPL feedback helps test the assumptions that are driving the design choices.

      • Read - code is read by the Clojure reader, passing any macros to the macro reader which converts those macros into Clojure code.
      • Evaluate - code is compiled into the host language (e.g. Java bytecode) and executed
      • Print - results of the code are displayed, either in the REPL or as part of the application.
      • Loop - the REPL is a continuous process that evaluates code, either a single expression or the whole application.

      Design decisions and valuable data from REPL experiments can be codified as specifications and unit tests

      Practicalli REPL Reloaded Workflow

      The principles of REPL driven development are implemented in practice using the Practicalli REPL Reloaded Workflow and supporting tooling. This workflow uses Portal to inspect all evaluation results and log events, hot-load libraries into the running REPL process and reloads namespaces to support major refactor changes.

      "},{"location":"introduction/repl-workflow/#evaluating-source-code","title":"Evaluating source code","text":"

      A REPL connected editor is the primary tool for evaluating Clojure code from source code files, displaying the results inline.

      Source code is automatically evaluated in its respective namespace, removing the need to change namespaces in the REPL with (in-ns) or use fully qualified names to call functions.

      Evaluate Clojure in Neovim with Conjure

      , e b evaluates the code in the current buffer

      Evaluate Clojure in a Terminal UI REPL

      Entering expressions at the REPL prompt evaluates the expression immediately, returning the result directly underneath

      "},{"location":"introduction/repl-workflow/#rich-comment-blocks-living-documentation","title":"Rich Comment blocks - living documentation","text":"

      The (comment ,,,) function wraps code that is only run directly by the developer using a Clojure aware editor.

      Expressions in rich comment blocks can represent how to use the functions that make up the namespace API. For example, starting/restarting the system, updating the database, etc. Expressions provide examples of calling functions with typical arguments and make a project more accessible and easier to work with.

      Clojure Rich Comment to manage a service

      (ns practicalli.gameboard.service)\n\n(defn app-server-start [port] ,,,)\n(defn app-server-start [] ,,,)\n(defn app-server-restart [] ,,,)\n\n(defn -main\n  \"Start the service using system components\"\n  [& options] ,,,)\n\n(comment\n  (-main)\n  (app-server-start 8888)\n  (app-server-stop)\n  (app-server-restart 8888)\n\n  (System/getenv \"PORT\")\n  (def environment (System/getenv))\n  (def system-properties (System/getProperties))\n  ) ; End of rich comment block\n

      Rich comment blocks are very useful for rapidly iterating over different design decisions by including the same function but with different implementations. Hide clj-kondo linter warnings for redefined vars (def, defn) when using this approach.

      ;; Rich comment block with redefined vars ignored\n#_{:clj-kondo/ignore [:redefined-var]}\n(comment\n  (defn value-added-tax []\n    ;; algorithm design - first idea)\n\n  (defn value-added-tax []\n    ;; algorithm design - second idea)\n\n  ) ;; End of rich comment block\n

      The \"Rich\" in the name is an honourary mention to Rich Hickey, the author and benevolent dictator of Clojure design.

      "},{"location":"introduction/repl-workflow/#design-journal","title":"Design Journal","text":"

      A journal of design decisions makes the code easier to understand and maintain. Code examples of design decisions and alternative design discussions are captured, reducing the time spent revisiting those discussions.

      Journals simplify the developer on-boarding processes as the journey through design decisions are already documented.

      A Design Journal is usually created in a separate namespace, although it may start as a rich comment at the bottom of a namespace.

      A journal should cover the following aspects

      • Relevant expressions use to test assumptions about design options.
      • Examples of design choices not taken and discussions why (saves repeating the same design discussions)
      • Expressions that can be evaluated to explain how a function or parts of a function work

      The design journal can be used to create meaningful documentation for the project very easily and should prevent time spent on repeating the same conversations.

      Example design journal

      Design journal for TicTacToe game using Reagent, ClojureScript and Scalable Vector Graphics

      "},{"location":"introduction/repl-workflow/#viewing-data-structures","title":"Viewing data structures","text":"

      Pretty print shows the structure of results from function calls in a human-friendly form, making it easier for a developer to parse and more likely to notice incorrect results.

      Tools to view and navigate code

      • Cider inspector is an effective way to navigate nested data and page through large data sets.
      • Portal Inspector to visualise many kinds of data in many different forms.

      "},{"location":"introduction/repl-workflow/#code-style-and-idiomatic-clojure","title":"Code Style and idiomatic Clojure","text":"

      Clojure aware editors should automatically apply formatting that follows the Clojure Style guide.

      Live linting with clj-kondo suggests common idioms and highlights a wide range of syntax errors as code is written, minimizing bugs and therefore speeding up the development process.

      Clojure LSP is build on top of clj-kondo

      Clojure LSP uses clj-kondo static analysis to provide a standard set of development tools (format, refactor, auto-complete, syntax highlighting, syntax & idiom warnings, code navigation, etc).

      Clojure LSP can be used with any Clojure aware editor that provides an LSP client, e.g. Spacemacs, Doom Emacs, Neovim, VSCode.

      Clojure Style Guide

      The Clojure Style guide provides examples of common formatting approaches, although the development team should decide which of these to adopt. Emacs clojure-mode will automatically format code and so will Clojure LSP (via cljfmt). These tools are configurable and should be tailored to the teams standard.

      "},{"location":"introduction/repl-workflow/#data-and-function-specifications","title":"Data and Function specifications","text":"

      Clojure spec is used to define a contract on incoming and outgoing data, to ensure it is of the correct form.

      As data structures are identified in REPL experiments, create data specification to validate the keys and value types of that data.

      ;; ---------------------------------------------------\n;; Address specifications\n(spec/def ::house-number string?)\n(spec/def ::street string?)\n(spec/def ::postal-code string?)\n(spec/def ::city string?)\n(spec/def ::country string?)\n(spec/def ::additional string?)\n\n(spec/def ::address   ; Composite data specification\n  (spec/keys\n   :req-un [::street ::postal-code ::city ::country]\n   :opt-un [::house-number ::additional]))\n;; ---------------------------------------------------\n

      As the public API is designed, specifications for each functions arguments are added to validate the correct data is used when calling those functions.

      Generative testing provides a far greater scope of test values used incorporated into unit tests. Data uses clojure.spec to randomly generate data for testing on each test run.

      "},{"location":"introduction/repl-workflow/#test-driven-development-and-repl-driven-development","title":"Test Driven Development and REPL Driven Development","text":"

      Test Driven Development (TDD) and REPL Driven Development (RDD) complement each other as they both encourage incremental changes and continuous feedback.

      Test Driven Development fits well with Hammock Time, as good design comes from deep thought

      • RDD enables rapid design experiments so different approaches can easily and quickly be evaluated .
      • TDD focuses the results of the REPL experiments into design decisions, codified as unit tests. These tests guide the correctness of specific implementations and provide critical feedback when changes break that design.

      Unit tests should support the public API of each namespace in a project to help prevent regressions in the code. Its far more efficient in terms of thinking time to define unit tests as the design starts to stabilize than as an after thought.

      clojure.test library is part of the Clojure standard library that provides a simple way to start writing unit tests.

      Clojure spec can also be used for generative testing, providing far greater scope in values used when running unit tests. Specifications can be defined for values and functions.

      Clojure has a number of test runners available. Kaocha is a test runner that will run unit tests and function specification checks.

      Automate local test runner

      Use kaocha test runner in watch mode to run tests and specification check automatically (when changes are saved)

      clojure -X:test/watch\n

      "},{"location":"introduction/repl-workflow/#continuous-integration-and-deployment","title":"Continuous Integration and Deployment","text":"

      Add a continuous integration service to run tests and builds code on every shared commit. Spin up testable review deployments when commits pushed to a pull request branch, before pushing commits to the main deployment branch, creating an effective pipeline to gain further feedback.

      • CircleCI provides a simple to use service that supports Clojure projects.
      • GitHub Workflows and GitHub actions marketplace to quickly build a tailored continuous integration service, e.g. Setup Clojure GitHub Action.
      • GitLab CI

      "},{"location":"introduction/repl-workflow/#live-coding-with-data-stuart-halloway","title":"Live Coding with Data - Stuart Halloway","text":"

      There are few novel features of programming languages, but each combination has different properties. The combination of dynamic, hosted, functional and extended Lisp in Clojure gives developers the tools for making effective programs. The ways in which Clojure's unique combination of features can yield a highly effective development process.

      Over more than a decade we have developed an effective approach to writing code in Clojure whose power comes from composing many of its key features. As different as Clojure programs are from e.g. Java programs, so to can and should be the development experience. You are not in Kansas anymore!

      This talk presents a demonstration of the leverage you can get when writing programs in Clojure, with examples, based on my experiences as a core developer of Clojure and Datomic.

      "},{"location":"introduction/writing-tips/","title":"Writing tips for MkDocs","text":"

      Making the docs more engaging using the mkdocs-material theme reference guide

      Configuring Colors

      Material for MkDocs - Changing the colors lists the primary and accent colors available.

      HSL Color Picker for codes to modify the theme style, overriding colors in docs/assets/stylesheets/extra.css

      "},{"location":"introduction/writing-tips/#hypertext-links","title":"Hypertext links","text":"

      Links open in the same browser window/tab by default.

      Add {target=_blank} to the end of a link to configure opening in a new tab

      [link text](url){target=_blank}\n
      "},{"location":"introduction/writing-tips/#buttons","title":"Buttons","text":"

      Convert any link into a button by adding {.md-button} class names to end of the markdown for a link, which uses .md-button-primary by default. Include target=_blank for buttons with links to external sites.

      [link text](http://practical.li/blog){.md-button target=_blank}\n

      Or specify a different class

      [link text](http://practical.li/blog){.md-button .md-button-primary}\n

      Add an icon to the button

      Practicalli Issues Practicalli Blog

      [:fontawesome-brands-github: Practicalli Issues](http://practical.li/blog){ .md-button .md-button-primary }\n[:octicons-heart-fill-24: Practicalli Blog](http://practical.li/blog){ .md-button .md-button-primary }\n

      Search all supported icons

      "},{"location":"introduction/writing-tips/#youtube-video","title":"YouTube video","text":"

      Use an iframe element to include a YouTube video, wrapping in a paragraph tag with center alignment to place the video in a centered horizontal position

      <p style=\"text-align:center\">\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/rQ802kSaip4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n</p>\n

      mkdocs material does not have direct support for adding a YouTube video via markdown.

      "},{"location":"introduction/writing-tips/#admonitions","title":"Admonitions","text":"

      Supported admonition types

      Note

      Use !!! followed by NOTE

      Adding a title

      Use !!! followed by NOTE and a \"title in double quotes\"

      Shh, no title bar just the text... Use !!! followed by NOTE and a \"\" empty double quotes

      Abstract

      Use !!! followed by ABSTRACT

      Info

      Use !!! followed by INFO

      Tip

      Use !!! followed by TIP

      Success

      Use !!! followed by SUCCESS

      Question

      Use !!! followed by QUESTION

      Warning

      Use !!! followed by WARNING

      Failure

      Use !!! followed by FAILURE

      Danger

      Use !!! followed by DANGER

      Bug

      Use !!! followed by BUG

      Example

      Use !!! followed by EXAMPLE

      Quote

      Use !!! followed by QUOTE

      "},{"location":"introduction/writing-tips/#collapsing-admonitions","title":"Collapsing admonitions","text":"Note

      Collapse those admonitions using ??? instead of !!!

      Replace with a title

      Use ??? followed by NOTE and a \"title in double quotes\"

      Expanded by default

      Use ???+, note the + character, followed by NOTE and a \"title in double quotes\"

      "},{"location":"introduction/writing-tips/#inline-blocks","title":"Inline blocks","text":"

      Inline blocks of text to make a very specific callout within text

      Info

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Adding something to then end of text is probably my favourite

      Info

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      "},{"location":"introduction/writing-tips/#code-blocks","title":"Code blocks","text":"

      Code blocks include a copy icon automatically

      Syntax highlighting in code blocks

      (defn my-function  ; Write a simple function\n  \"With a lovely doc-string\"\n  [arguments]\n  (map inc [1 2 3]))\n

      Give the code block a title using title=\"\" after the backtics and language name

      src/practicalli/gameboard.clj
      (defn my-function\n  \"With a lovely doc-string\"\n  [arguments]\n  (map inc [1 2 3]))\n

      We all like line numbers, especially when you can set the starting line

      src/practicalli/gameboard.clj
      (defn my-function\n  \"With a lovely doc-string\"\n  [arguments]\n  (map inc [1 2 3]))\n

      Add linenums=42 to start line numbers from 42 onward

      clojure linenums=\"42\" title=\"src/practicalli/gameboard.clj\"\n
      "},{"location":"introduction/writing-tips/#annotations","title":"Annotations","text":"

      Annotations in a code block help to highlight important aspects. Use the comment character for the language followed by a space and a number in brackets

      For example, in a shell code block, use # (1) where 1 is the number of the annotation

      Use a number after the code block to add the text for the annotation, e.g. 1.. Ensure there is a space between the code block and the annotation text.

      ls -la $HOME/Downloads  # (1)\n
      1. I'm a code annotation! I can contain code, formatted text, images, ... basically anything that can be written in Markdown.

      Code blocks with annotation, add ! after the annotation number to suppress the # character

      (defn helper-function\n  \"Doc-string with description of function purpose\" ; (1)!\n  [data]\n  (merge {:fish 1} data)\n  )\n
      1. Always include a doc-string in every function to describe the purpose of that function, identifying why it was added and what its value is.

      GitHub action example with multiple annotations

      name: ci # (1)!\non:\n  push:\n    branches:\n      - master # (2)!\n      - main\npermissions:\n  contents: write\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v4\n        with:\n          python-version: 3.x\n      - run: pip install mkdocs-material # (3)!\n      - run: mkdocs gh-deploy --force\n
      1. You can change the name to your liking.

      2. At some point, GitHub renamed master to main. If your default branch is named master, you can safely remove main, vice versa.

      3. This is the place to install further [MkDocs plugins] or Markdown extensions with pip to be used during the build:

        pip install \\\n  mkdocs-material \\\n  mkdocs-awesome-pages-plugin \\\n  ...\n
      "},{"location":"introduction/writing-tips/#highlight-lines-in-code-blocks","title":"Highlight lines in code blocks","text":"

      Add highlight line meta data to a code block after the opening backticks and code block language.

      hl_lines=\"2\" highlights line 2 in the codeblock

      hl_lines=\"2 3 4\" highlights line 2, 3 and 4 in the codeblock

      (defn my-function\n  \"With a lovely doc-string\"\n  [arguments]\n  (map\n   inc\n   [1 2 3]))\n
      "},{"location":"introduction/writing-tips/#embed-external-files","title":"Embed external files","text":"

      --8<-- in a code block inserts code from a source code file or other text file

      Specify a local file from the root of the book project (the directory containing mkdocs.yml)

      Scheduled Version Check GitHub Workflow from source code file scheduled version check
      ---\n# ------------------------------------------\n# Scheduled check of versions\n# - use as non-urgent report on versions\n# - Uses POSIX Cron syntax\n#   - Minute [0,59]\n#   - Hour [0,23]\n#   - Day of the month [1,31]\n#   - Month of the year [1,12]\n#   - Day of the week ([0,6] with 0=Sunday)\n#\n# Using liquidz/anta to check:\n# - GitHub workflows\n# - deps.edn\n# ------------------------------------------\n\nname: \"Scheduled Version Check\"\non:\n  schedule:\n    # - cron: \"0 4 * * *\" # at 04:04:04 ever day\n    # - cron: \"0 4 * * 5\" # at 04:04:04 ever Friday\n    - cron: \"0 4 1 * *\" # at 04:04:04 on first day of month\n  workflow_dispatch: # Run manually via GitHub Actions Workflow page\n\njobs:\n  scheduled-version-check:\n    name: \"Scheduled Version Check\"\n    runs-on: ubuntu-latest\n    steps:\n      - run: echo \"\ud83d\ude80 Job automatically triggered by ${{ github.event_name }}\"\n      - run: echo \"\ud83d\udc27 Job running on ${{ runner.os }} server\"\n      - run: echo \"\ud83d\udc19 Using ${{ github.ref }} branch from ${{ github.repository }} repository\"\n\n      - name: \"Checkout code\"\n        uses: actions/checkout@v4\n      - run: echo \"\ud83d\udc19 ${{ github.repository }} repository was cloned to the runner.\"\n\n      - name: \"Antq Check versions\"\n        uses: liquidz/antq-action@main\n        with:\n          excludes: \"\"\n          skips: \"boot clojure-cli pom shadow-cljs leiningen\"\n\n      # Summary\n      - run: echo \"\ud83c\udfa8 library versions checked with liquidz/antq\"\n      - run: echo \"\ud83c\udf4f Job status is ${{ job.status }}.\"\n
      Practicalli Project Templates Emacs project configuration - .dir-locals.el
      ((clojure-mode . ((cider-preferred-build-tool . clojure-cli)\n                  (cider-clojure-cli-aliases . \":test/env:dev/reloaded\"))))\n

      Code example reuse

      Use an embedded local or external file (URL) when the same content is required in more than one place in the book.

      An effective way of sharing code and configuration mutliple times in a book or across multiple books.

      "},{"location":"introduction/writing-tips/#content-tabs","title":"Content tabs","text":"

      Create in page tabs that can also be

      Setting up a project

      Clojure CLILeiningen
      clojure -T:project/new :template app :name practicalli/gameboard\n
      lein new app practicalli/gameboard\n

      Or nest the content tabs in an admonition

      Run a terminal REPL

      Clojure CLILeiningen
      clojure -T:repl/rebel\n
      lein repl\n
      "},{"location":"introduction/writing-tips/#diagrams","title":"Diagrams","text":"

      Neat flow diagrams

      Diagrams - Material for MkDocs

      graph LR\n  A[Start] --> B{Error?};\n  B -->|Yes| C[Hmm...];\n  C --> D[Debug];\n  D --> B;\n  B ---->|No| E[Yay!];

      UML Sequence Diagrams

      sequenceDiagram\n  Alice->>John: Hello John, how are you?\n  loop Healthcheck\n      John->>John: Fight against hypochondria\n  end\n  Note right of John: Rational thoughts!\n  John-->>Alice: Great!\n  John->>Bob: How about you?\n  Bob-->>John: Jolly good!

      state transition diagrams

      stateDiagram-v2\n  state fork_state <<fork>>\n    [*] --> fork_state\n    fork_state --> State2\n    fork_state --> State3\n\n    state join_state <<join>>\n    State2 --> join_state\n    State3 --> join_state\n    join_state --> State4\n    State4 --> [*]

      Class diagrams - not needed for Clojure

      Entity relationship diagrams are handy though

      erDiagram\n  CUSTOMER ||--o{ ORDER : places\n  ORDER ||--|{ LINE-ITEM : contains\n  LINE-ITEM {\n    customer-name string\n    unit-price int\n  }\n  CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
      "},{"location":"introduction/writing-tips/#keyboard-keys","title":"Keyboard keys","text":"

      Represent key bindings with Keyboard keys. Each number and alphabet character has their own key.

      • 1 ++1++ for numbers
      • l ++\"l\"++ for lowercase character
      • U ++u++ for uppercase character or ++\"U\"++ for consistency

      Punctionation keys use their name

      • Space ++spc++
      • , ++comma++
      • Left ++arrow-left++

      For key sequences, place a space between each keyboard character

      • Space g s ++spc++ ++\"g\"++ ++\"s\"++

      For key combinations, use join they key identifies with a +

      • Meta+X ++meta+x++
      • Ctrl+Alt+Del ++ctrl+alt+del++

      MkDocs keyboard keys reference

      "},{"location":"introduction/writing-tips/#images","title":"Images","text":"

      Markdown images can be appended with material tags to set the size of the image, whether to appear on light or dark theme and support lazy image loading in browsers

      SizeLazy LoadingAlignTheme SpecificAll Image Attributes

      {style=\"height:150px;width:150px\"} specifies the image size

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png#only-dark){style=\"height:150px;width:150px\"}\n

      {loading=lazy} specifies an image should lazily load in the browser

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png){loading=lazy}\n

      {aligh=left} or {aligh=right} specifies the page alignment of an image.

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png#only-dark){align=right}\n![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-dark.png#only-light){align=right}\n

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      ![Kitty Logo](image/kitty-light.png#only-dark) or ![Kitty Logo](image/kitty-light.png#only-light) specifies the theme the image should be shown, allowing different versions of images to be shown based on the theme.

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png#only-dark){style=\"height:150px;width:150px\"}\n![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-dark.png#only-light){style=\"height:150px;width:150px\"}\n
      Use the theme toggle in the top nav bar to see the icon change between light and dark.

      Requires the color pallet toggle

      Alight right, lazy load and set image to 150x150

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png#only-dark){align=right loading=lazy style=\"height:64px;width:64px\"}\n![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-dark.png#only-light){align=right loading=lazy style=\"height:64px;width:64px\"}\n

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      "},{"location":"introduction/writing-tips/#lists","title":"Lists","text":"

      Task lists

      • Lorem ipsum dolor sit amet, consectetur adipiscing elit
      • Vestibulum convallis sit amet nisi a tincidunt
      • In hac habitasse platea dictumst
      • In scelerisque nibh non dolor mollis congue sed et metus
      • Praesent sed risus massa
      • Aenean pretium efficitur erat, donec pharetra, ligula non scelerisque

      Task List example

      - [x] Lorem ipsum dolor sit amet, consectetur adipiscing elit\n- [ ] Vestibulum convallis sit amet nisi a tincidunt\n    * [x] In hac habitasse platea dictumst\n    * [x] In scelerisque nibh non dolor mollis congue sed et metus\n    * [ ] Praesent sed risus massa\n- [ ] Aenean pretium efficitur erat, donec pharetra, ligula non scelerisque\n
      "},{"location":"introduction/writing-tips/#tooltips","title":"Tooltips","text":"

      The humble tool tip

      Hover me

      with references

      Hover me

      Icon tool tip with a title

      "},{"location":"introduction/writing-tips/#abreviations","title":"Abreviations","text":"

      The HTML specification is maintained by the W3C.

      [HTML]: Hyper Text Markup Language [W3C]: World Wide Web Consortium

      "},{"location":"introduction/writing-tips/#magic-links","title":"Magic links","text":"

      MagicLink can auto-link HTML, FTP, and email links. It can auto-convert repository links (GitHub, GitLab, and Bitbucket) and display them in a more concise, shorthand format.

      Email Practicalli

      Practicalli Neovim

      "},{"location":"neovim-basics/","title":"Using Neovim","text":"

      The fundamental controls of Neovim which apply across all editing tasks.

      "},{"location":"neovim-basics/#fundamentals","title":"Fundamentals","text":"

      File Buffer Window and Tab page Multi-modal Editing

      "},{"location":"neovim-basics/#editing-tools","title":"Editing Tools","text":"

      Multiple Cursors

      "},{"location":"neovim-basics/#writing-tools","title":"Writing Tools","text":"

      Snippets

      "},{"location":"neovim-basics/#development-tools","title":"Development Tools","text":"

      Comments Clojure Development Version Control

      Format and Lint tools are installed via Mason

      "},{"location":"neovim-basics/#spellcheck","title":"Spellcheck","text":"AstroNvim

      SPC u s toggles spellcheck, marking misspelt words with a rew wavy underline

      ] s jumps to next misspelt word, [ s jumps to previous misspelt word,

      z = shows numbered list of possible words, enter the number next to the work to replace the misspelt word.

      z g to add the current word to the spell list, infroming spellcheck that this is a correct word.

      "},{"location":"neovim-basics/comments/","title":"Comments","text":"

      comment.nvim toggles a comment for lines, visual selections or for motions

      gcc comment current line, 4gcc comment current line and next 4 lines

      gc comment region or use with motion e.g. gcap comment paragraph,

      gc in operator pending mode to target a comment TODO: what is operator pending mode

      :7,17Commentary comment a range

      :g/TODO/Commentary as part of a :global invocation

      gcgc removes comments from a set of adjacent commented lines.

      "},{"location":"neovim-basics/file-buffer-window-tab/","title":"Files Buffers Windows and Tabs","text":"

      Files are text written to perminant storage, e.g. disk or usb drive and are names with an extension that represents the file type, e.g. .clj for clojure, .md for markdown, etc.

      A Buffer hold the contents of a file or any other information from processes, e.g. the REPL evaluation log.

      Windows are a view on a buffer and windows can swap which buffer they show. Multiple windows, also known as splits, can be present in a Neovim frame.

      A tab page (or tab) can hold one or more windows and multiple tab pages can be shown on a tab-line.

      "},{"location":"neovim-basics/file-buffer-window-tab/#files","title":"Files","text":"

      Use Neo-tree.nvim to visually navigate and manage files using a tree view of the current project. Files and directories can be added, renamed, moved and deleted.

      Use Telescope to select files, typing a name narrows the file list.

      "},{"location":"neovim-basics/file-buffer-window-tab/#using-neo-tree","title":"Using Neo-tree","text":"AstronvimPracticalli Neovim Config Redux

      Space e toggles neo-tree file browser

      Space o toggles between buffer and neo-tree

      Space f t t to open file explorer

      Within Neo-tree:

      h j k l to navigate the file tree hierachy

      < and > to navigate between File, Bufs and Git sources tabs

      ? shows neotree help, listing key bindings

      a adds a file, prompting for a name relative to the directory where a was pressed. The name can include new directories to be created. A name ending with ++forward-slash++ will create a directory rather than a file.

      d deletes the current file or directory (including sub-directories), a conformation prompt is shown

      r to rename a file or directory (use move to change the path)

      m to move a file or directory, optionally renaming too

      Neotree icons

      • yellow dot - unsaved changes
      • pencil - git added changes
      • cross - git deleted changes
      • Warning triangle - lsp diagnostics issues
      "},{"location":"neovim-basics/file-buffer-window-tab/#telescope","title":"Telescope","text":"

      Telescope provides a selector which will narrow the list of matches as a pattern is typed, providing a fast way to find an item in a list.

      Telescope provides a preview of the selected file (only if there is sufficient space in the Neovim frame)

      File lists are relative to the directory Neovim was opened from (or Path subsequently set in Neovim).

      AstroNvimPracticalli Neovim Config Redux

      SPC f f selector for files within the scope of the current directory path. SPC f F to also show hidden files from the current directory path.

      SPC f a selector for AstroNvim user configuration files

      SPC f p selector for previously opened files (oldfiles)

      SPC f f to list files within the scope of the current directory path.

      SPC f b provides a file browser to open files, navigate the file space and create new files and directories

      "},{"location":"neovim-basics/file-buffer-window-tab/#save-file","title":"Save File","text":"

      Files and directories are created in the path given, relative to the directory in which Neovim was opened.

      A file must exist for Neovim to write to it. Neo-tree and Telescope can be used to create files and directories, as can a terminal and the command line integration (!)

      AstronvimPracticalli Neovim Config ReduxNeovim

      Space w will write all buffer changes to the associate file.

      Space n creates a new buffer that can be written to a given file using :write path/to/filename

      :write path/to/filename will write the current buffer to a new file.

      SPC f b ESC C to create a new file or directory. The base path is shown in the command bar. Type the name of directories and file name as required. RTN to create or ESC to cancel. The newly created directories or file name appears n the Telescope list and scan be selected for opening.

      Telescope Normal mode and help

      ESC in Telescope to switch to Normal mode and use comannds, c for Create, r to rename.

      ? to show all the commands available in Telescope

      :lcd to set the current local directory

      :write path/to/filename will write the current buffer to a new file

      :!mkdir path/to/directory will create a new directory

      If a file is already opened, i.e. with :edit, there is some short-hand syntax to simplify the typing

      :!mkdir -p %:h

      -p option createst any parts of the path required to make the full path

      % is the neovim name of the current file

      :h for the current directory (the \u201chead\u201d of the path).

      ! is the NeoVim terminal shell command, e.g. :!mkdir -p path/to/new/directory creates a new directory and any intermediate path

      "},{"location":"neovim-basics/file-buffer-window-tab/#swap-file","title":"Swap file","text":"

      Neovim creates a swap file, .swp, containing the changes made in a buffer to minimise loss should there be an issue with the computer or Neovim. Changes are written to the swap file after 200 characters or after 4 seconds pause.

      Swap file location

      :swapname shows the full path to the swap file for the current buffer, e.g.

      /home/practicalli/.local/state/astronvim/swap//%home%practicalli%projects%practicalli%books%neovim%docs%neovim-basics%files-buffers-windows.md.swp`\n

      :preserve command will write all text from current buffer to the swap file.

      :recover command overwrites the current buffer with the data from the swap file. :recover! command must be use if the buffer has newer changes than the swap file. Add a filename after the command to recover to a different file than that contained in the current buffer.

      Opening a file checks if there is an associated swap file and prompts the user

      • (A)bort opening the file
      • (D)elete the swap file
      • (E)dit anyway, select if the file is newer than the swap file
      • (R)ecover the data in the swap file into the file buffer

      :edit after the file is open also prompts if there is a swap file. Selecting (D)elete will delete the swap file without changing the current buffer

      "},{"location":"neovim-basics/file-buffer-window-tab/#buffers","title":"Buffers","text":"AstroNvimPracticalli Neovim Config Redux

      SPC f b selector for currently open buffers

      SPC b b to select a buffer from the tab line, pressing the character that appears next to the buffer tab (case sensitive)

      SPC b D to delete a buffer from the tab line, pressing the character that appears next to the buffer tab (case sensitive)

      Open multiple buffers when starting Neovim by specifying multiple files to open

      astro README.md deps.edn src/practicalli/playground.clj test/practicalli/playground.clj\n

      SPC b b switch between buffers in the current window, using a Telescope popup that lists all current buffers (includes files, Conjure REPL Log, etc.).

      SPC b n (:next) and SPC b n (:previous) to cycle through buffers in the current window

      SPC TAB (C-^) opens the previous buffer, useful to toggle between two buffers in the same window

      Use Telescope to switch between buffers

      Open multiple buffers when starting Neovim by specifying multiple files to open

      nvim README.md deps.edn src/practicalli/playground.clj test/practicalli/playground.clj\n
      "},{"location":"neovim-basics/file-buffer-window-tab/#buffer-text-wrapping","title":"Buffer text wrapping","text":"

      The test in a buffer is not wrapped by default. Set and unset soft text wrapping in a buffer

      AstroNvimPracticalli Neovim Config ReduxNeovim

      SPC u w toggles wrapping of text

      line wrap not enabled in configuration by default.

      fnl/config/init.fnl
      (nvim.ex.set :nowrap)\n

      :set wrap to set soft wrapping on current buffer

      :set nowrap to show lines in full (scroll sideways to see lines longer than the window)

      "},{"location":"neovim-basics/file-buffer-window-tab/#windows","title":"Windows","text":"

      Windows can be active (contains the cursor), hidden (open but not shown) or inactive.

      AstroNvimPracticalli Neovim Config ReduxNeovim

      \\ creates an horizontal split

      SPC q removes the current split

      SPC h / SPC l to jump to left / right buffer, SPC j / SPC k to jump to buffer below / above

      SPC b b to list current buffers and switch between them using telescope

      C-w and hjkl to navigate windows is the classic Vim approach

      C-w menu to manage Windows, also known as splits.

      C-w with one of hjkl will move the cursor to the next window in that direction. Also works with arrow keys.

      C-w w toggle between open windows

      :q or C-w q closes the active window, closing Neovim if it is the last active window.

      :wincmd can be used as an alternative to the Normal mode key bindings

      Open file in a new window

      :sp relative-or-full-filename-path\n

      Resize windows

      C-w -, +, < or > for vertical or horizontal size adjustment

      "},{"location":"neovim-basics/file-buffer-window-tab/#tab-pages","title":"Tab pages","text":"

      A Tab page can hold one or more tabs and are useful for grouping different types of files and information.

      A Tab page holds one or more windows, each window is a view on a buffer, a buffer holds the contents of a file or any other information in the editor memory (repl log, etc).

      A tab page can provide a logical grouping of windows, e.g. Clojure source code in one tab, tests in a second tab and REPL log in a third.

      Neovim window commands may be constrained within the bounds of a tab page (without using the :tab modifier)

      Tab pages are often referred to as tabs.

      AstroNvim

      g Tab jump to previously selected tab, commonly used to toggle between two tabs (Practicalli AstroNvim mapping)

      g t jump to next tab page

      g T jump to previous tab page

      "},{"location":"neovim-basics/multi-modal-editing/","title":"Multi-modal Editing","text":"

      TODO: Add multi-modal editing in Neovim guide

      Practicalli Spacemacs has useful reference content on multi-modal editing (Evil mode).

      Most of this content is the same in Neovim with a few exceptions

      "},{"location":"neovim-basics/multi-modal-editing/#selecting-text","title":"Selecting text","text":"

      vi) selects all the text within (), e.g. (http://oldwebsite.doh)

      "},{"location":"neovim-basics/multi-modal-editing/#surround","title":"Surround","text":"

      viw selects the current word, using j/k to modify the selection where required. o toggles which end of the selection is expanded/shrunk

      s substitues the selection, type the characters to surround the selection.

      p to pase the original text

      "},{"location":"neovim-basics/multi-modal-editing/#nvim-surround","title":"nvim-surround","text":"

      nvim-surround provides enhancments over the neovim surround command.

      nvim-surround included in Practicalli AstroNvim Config

      "},{"location":"neovim-basics/multi-modal-editing/#visual-mode","title":"Visual Mode","text":"

      viw to select the current word (visual in word)

      S on a visual selection to surround with next that character, e.g. S) to surround with parens.

      Closing paren surrounds without spaces

      ), ], } surrounds the selected text without spaces between the text and the open and closing parens.

      (, [, { surrounds the selected text with a space between the text and the open and closing parens.

      "},{"location":"neovim-basics/multi-modal-editing/#normal-mode","title":"Normal mode","text":"

      cs inside an existing pair of characters to change them to another pair of surrounding characters, e.g. cs(} to change (text) to {text}

      ds inside a pair of surrounding characters to delete them, e.g. ds( to change (text) to text

      ys you surround followed by motion and character, e.g. ysw) surrounds word with (parens)

      yS to surround current line

      ySS to surround current line, placing characters on new lines, e.g. ySS{ will change \"Olical/conjure\" to:

      {\n    \"Olical/conjure\"\n}\n

      The three \"core\" operations of add/delete/change can be done with the keymaps ys{motion}{char}, ds{char}, and cs{target}{replacement}, respectively. For the following examples, * will denote the cursor position:

      Old text                    Command         New text\n----------------------------------------------------\nsurr*ound_words             ysiw)           (surround_words)\n*make strings               ys$\"            \"make strings\"\n[delete ar*ound me!]        ds]             delete around me!\nremove <b>HTML t*ags</b>    dst             remove HTML tags\n'change quot*es'            cs'\"            \"change quotes\"\n<b>or tag* types</b>        csth1<CR>       <h1>or tag types</h1>\ndelete(functi*on calls)     dsf             function calls\n

      Neovim help provides details on using nvim-surround

      :help nvim-surround.usage\n
      "},{"location":"neovim-basics/multi-modal-editing/#web-links","title":"Web Links","text":"

      g x on a URL to open in the default browser

      "},{"location":"neovim-basics/multiple-cursors/","title":"Multiple cursors","text":"

      Visual-Multi (VM) is a multiple selections/cursors plugin that uses modal editing and provide visual feedback when editing multiple lines simultaneously.

      Mulitple cursors is generally useful when editing smilarly structured lines with diffferent content. Cursors are moved by column position or by using vim motions.

      AstroNvimPracticalli Neovim Config Redux

      Space g m opens the visual-multi menu

      Using visual select is a simple way to edit multiple lines as the same time.

      Space g m c on visually selected lines creates a cursor on each line.

      Esc or f d to close multiple cursors.

      \\ \\ c creates a cursor at the start of every visual selection line

      \\ \\ \\ toggle cursor at position

      "},{"location":"neovim-basics/multiple-cursors/#search-and-replace","title":"Search and Replace","text":"

      Select a text pattern to search for and use multiple cursors to add a cursor each match, then change each selected occurance concurrently.

      AstroNvimPracticalli Neovim Config Redux

      Select the pattern with visual select

      Space g m a on each matching text pattern in the buffer

      c to change the original text and type the new pattern. All cursors will update concurrently.

      Esc or f d to close multiple cursors.

      \\ \\ a creates a cursor at the start of every visual selection line

      \\ \\ \\ toggle cursor at position

      c to change the original text and type the new pattern. All cursors will update concurrently.

      Esc or f d to close multiple cursors.

      "},{"location":"neovim-basics/multiple-cursors/#command-quick-reference","title":"Command quick reference","text":"

      :help g:VM_maps for a reference of all mappings and instructions on how to change them

      AstroNvim

      SPC g m opens the Visual-Multi menu in normal mode

      g m opens the Visual-Multi menu in visual mode

      c to add a cursor to every line in the visually selected region

      Practicalli Neovim Config Redux

      \\ \\ is the leader for multiple cursors and will show the visual-multi menu in which-key.

      These commands cover the large majority of use cases for multiple cursors.

      Action Key Command Add Cursor at Position \\\\\\ vm-add-cursor Alignm VM cursors with cursor \\\\a vm-align Select All Words \\\\A vm-select-all Transposition \\\\t vm-transpose Toggle Mappings \\\\<Space> vm-mappings-toggle Find with Regex \\\\/ vm-regex-search Reselect Last \\\\gS vm-reselect-last

      Once visual-multi has started the vm-mappings-buffer mappings are available:

      Action Key Command Find Word <C-n> vm-find-word Next/Previous/Skip n / N / q vm-find-next Remove Region Q vm-remove-region Add Cursors Down/Up <C-Down> / <C-Up> vm-add-cursors Select Right/Left <S-Right>, <S-Left> vm-shift-select Slash motion g/ vm-slash Select Operator s vm-select-operator Find Operator m vm-find-operator

      NOTE: C-n conflicts with the Termux binding for naming a session

      "},{"location":"neovim-basics/multiple-cursors/#searching","title":"Searching","text":"

      g/ to search for a match to add when visual-multi is active, rather than the usual / vim search.

      n and N can't be used to repeat the search, as they are used to get the next visual-multi match.

      "},{"location":"neovim-basics/multiple-cursors/#find-with-regex","title":"Find with Regex","text":"

      \\ \\ / followed by a regex pattern will create a selection with that pattern.

      n and N finds the next occurrence of the regex pattern

      "},{"location":"neovim-basics/multiple-cursors/#smart-case-change","title":"Smart case change","text":"

      gc In extend-mode will use smartcase to change a selection

      • at main cursor, text is always inserted as typed
      • at other cursors, if region text was fully uppercased, replacement will be uppercased as well
      • if the region text was capitalized, the replacement will be as well
      "},{"location":"neovim-basics/multiple-cursors/#filter-regions","title":"Filter regions","text":"

      \\ \\ f filter out (remove) regions based on pattern or expression.

      C-x to cycle filtering method:

      • pattern: remove regions that don't match the pattern
      • !pattern: remove regions that match the pattern
      • expression: remove regions that don't match the expression (same as below)
      "},{"location":"neovim-basics/multiple-cursors/#transform-regions-with-expression","title":"Transform regions with expression","text":"

      \\ \\ e to transform a region with a vim expression, run on each region

      Placeholders can be used in the expression

      • `%t~ region's text as a string (as-is)
      • `%f~ region's text evaluated as a floating point number
      • `%n~ region's text evaluated as an integer number
      • `%i~ region's index
      • `%N~ total number of regions

      Examples: - %f * 0.5 divide text of all regions by 2 - %t .\" \". %i .\" / \". %n append index / total to text of each region - %i%2 ? %t : toupper(%t) uppercase all odd regions (1,3,5...) - %i%3-2 ? %t : '' delete every third region

      "},{"location":"neovim-basics/multiple-cursors/#vm-motions","title":"VM Motions","text":"

      visual-multi supports vim motions although they behave differently as their result is dependent on the mode:

      • cursor mode will move cursors
      • extend mode motions extend selections

      Unless multiline-mode is enabled motions are restricted to the current line and cannot cross line boundaries

      Some object-motions and various-motions require multiline-mode and aliased to avoid conflict with VM mappings:

      vim VM~ Description / g/ to next match (for all regions) ( ( [count] sentences backward ) ) [count] sentences forward { { [count] paragraphs backward } } [count] paragraphs forward [( g( go to [count] previous unmatched '(' [{ g{ go to [count] previous unmatched '{' ]) g) go to [count] next unmatched ')' ]} g} go to [count] next unmatched '}'"},{"location":"neovim-basics/multiple-cursors/#vm-operators","title":"vm-operators","text":"

      Visual-Multi supports several operators by default:

      • y / d / c to yank / delete / change
      • gu / gU to change text case

      Visual-Multi uses its own registers that are lists of strings. One element for each region that is yanked/deleted.

      There is also built-in support for:

      • vim-surround e.g. ysiw( to enclose in parentheses
      • vim-abolish e.g. cr_ to change current word to snake case

      `:help g:VM_user_operators to disccover how to doefine other operators

      "},{"location":"neovim-basics/multiple-cursors/#vm-multiline-mode","title":"vm-multiline-mode","text":"

      In normal and insert mode, cursors and selections are kept within their own line. Cursors are blocked from moving off the current line to the next line.

      M enables multiline-mode that allows cuursors to move onto another line.

      Multiline mode must be enabled for an object motions, or they will fail. See |vm-motions|.

      "},{"location":"neovim-basics/multiple-cursors/#alignment","title":"Alignment","text":"

      \\\\a aligns by setting the minimum column to the highest of all regions \\\\< aligns by character, or [count] characters \\\\> aligns by regex pattern

      In extend-mode selections are collapsed to cursors first, although will work regardless.

      "},{"location":"neovim-basics/multiple-cursors/#replace-pattern-in-regions","title":"Replace pattern in regions","text":"

      R to replace with a pattern and then the replacement text

      substitution will take place in all selected regions, leaving unselected text untouched.

      Only working in |extend-mode|. When |R| is pressed in |cursor-mode|, it will start |vm-replace-mode| instead.

      "},{"location":"neovim-basics/multiple-cursors/#subtract-pattern-from-regions","title":"Subtract pattern from regions","text":"

      \\\\s subtract the entered pattern from regions, splitting them. Only working in |extend-mode|.

      "},{"location":"neovim-basics/multiple-cursors/#transposition","title":"Transposition","text":"

      \\ \\ t swaps the contents of selections, cycling them if there are more than two.

      If there is an equal number of selections in each line, swapping takes place within the same line only. Only in |extend-mode|.

      "},{"location":"neovim-basics/multiple-cursors/#duplication","title":"Duplication","text":"

      \\ \\ d duplicates in place the contents of the selections, reselecting the original ones. Only in extend-mode.

      "},{"location":"neovim-basics/multiple-cursors/#shift-selections","title":"Shift Selections","text":"

      and move the selections right or left, preserving the surroundings."},{"location":"neovim-basics/multiple-cursors/#case-conversion","title":"Case conversion","text":"

      \\\\C runs on inner words in cursor mode

      • u lowercase
      • U UPPERCASE
      • C Captialize
      • t Title Case
      • c camelCase
      • P PascalCase
      • s snake_case
      • S SNAKE_UPPERCASE
      • - dash-case
      • . dot.case
      • <space> space case
      "},{"location":"neovim-basics/multiple-cursors/#modes","title":"Modes","text":"

      cursor-mode and extend-mode are two Visual-Multi modes, roughly corresponding to normal-mode and visual-mode

      TAB switches between cursor-mode and extended-mode

      "},{"location":"neovim-basics/multiple-cursors/#vm-cursor-mode","title":"VM Cursor Mode","text":"

      cursor-mode commands expect a motion, e.g. c should be followed by a text object to be changed.

      Key Description operators see vm-operators motions see vm-motions | set column for all cursors (to current column or [count]) r replace single character R enter vm-replace-mode ~ change case of single character & repeat last substitution <C-A> increase numbers <C-X> decrease numbers g<C-A> progressively increase numbers (v_g_CTRL-A) g<C-X> progressively decrease numbers (v_g_CTRL-X)

      You can enter |insert-mode| with i, I, a, A, and only from cursor mode also with o and O.

      Also see vm-motions for supported motions in VM (some with differences).

      "},{"location":"neovim-basics/multiple-cursors/#vm-extend-mode","title":"VM Extend Mode","text":"

      extend-mode is like having multiple visual selections. motions extend the slections and change / yank / delete commands don't wait for a motion, just like in visual mode.

      Even the key o works as in visual mode, inverting the anchor of the selections.

      Some commands are specific to |extend-mode|, such as:

      • s vim-surround
      • R replace pattern in regions
      • \\\\s split regions by pattern
      • \\\\e transform regions text with vim expression

      Some commands enforce cursor-mode when run from extend-mode:

      • <C-A> increase numbers
      • <C-X> decrease numbers

      Others can use a different mapping:

      • gu/gU change case (instead of vim u / U)
      • o and O mappings are used to invert the facing of the selected regions and not to start insert mode.
      "},{"location":"neovim-basics/notifications/","title":"Notifications","text":"Notifications only in AstroNvim, not the Practicalli Neovim configuration

      Space f n lists the history of notifications for the current sesion

      Enter to open the highlighted item in the list in its own popup

      Notification popups show information, warnings and errors.

      "},{"location":"neovim-basics/notifications/#configure-notifications","title":"Configure notifications","text":"

      Notifications are controlled by nvim-notify

      Practicalli astronvim-config overrides several default values in plugins/core.lua

      • top_down position of notifications, false shows popups from bottom of screen
      • timeout value controls how long a popup displays, defautl 3000
      • level of information displayed, level 3 hides less important information, e.g. file write messages, default 5

      Practicalli Configuration for notifications

      plugins/core.lua
        -- Configure notify popups\n  {\n    \"rcarriga/nvim-notify\",\n    opts = {\n      top_down = false,\n      timeout = 1000,\n      -- log level - 3 hide file write messages - default 5\n      level = 3,\n      -- background_color = \"#000000\",\n    },\n  },\n

      Noice uses nvim-notify configuration

      Noice replaces the UI for messages, command line and popup menus, although uses the configuration of nvim-notify for position and popup timing.

      "},{"location":"neovim-basics/plugin-manager/","title":"Plugin Manager","text":"

      Neovim community provides a wide range of plugins to greatly extend the features of Neovim

      There is a wide range of plugin managers too, including a built-in plugin manager in Neovim.

      • Lazy - AstroNvim Config
      • Packer - Practicalli Neovim Config Redux

      Lazy plugin manager recommeded

      Practicalli recommends Lazy plugin manager as it feels much easier to use and has a more engaing and understandable user interface

      Neovim evolving

      Neovim and its plugins are evolving quite rapidly, so it is recommended to update plugins if there are issue or when a newer version of Neovim has been installed

      Plugin issue are not that common and typically fixed quite quickly by the community

      AstroNvimPracticalli Neovim Config Redux

      Lazy plugin manager

      SPC P u to update packages to their latest versions (:PackerUpdate). Details of updated changes will be shown at the end of the update.

      r in the package update screen gives the option to revert an update if something has gone wrong (although this seem to be a rare issue).

      When packages are all at the latest available version, Packer update reports packages already up to date.

      Packer downloads packages and documentation from the Internet, so a connection is required

      "},{"location":"neovim-basics/plugin-manager/#package-list-and-documentation","title":"Package List and documentation","text":"AstroNvimPracticalli Neovim Config Redux

      Lazy plugin manager

      SPC P l to list the current packages added to the configuration

      Selecting a package will display the website documentation for the package (although this may be in HTML so not the cleanest way to read the docs).

      "},{"location":"neovim-basics/plugin-manager/#adding-packages","title":"Adding packages","text":"AstroNvimPracticalli Neovim Config Redux

      Lazy plugin manager

      Add package names as keywords in the use expression in fnl/config/plugin.fnl file.

      :requires to add a package that is a dependency for the package being added

      :mod defines the namespace that contains the package configuration, typically a setup function with options. The namespace matches the file name under fnl/config/plugin

      SPC P i to install packages that have been added to fnl/config/plugin.fnl

      q to quit once all packages are up to date

      "},{"location":"neovim-basics/search-replace/","title":"Search and Replace","text":"

      :substitute or :s vim command will highlight the matches for a text pattern and substitute for a new pattern

      Built-in help for the command

      :help :substitute\n
      Multiple cursors can also be used for multiple substitutions

      multiple cursors created on each occurance can be used to search and replace a pattern

      Subsitute the first matching patterns in the current line

      :s/current-pattern/new-pattern\n

      Subsitute all the matching patterns in the current line, g representing all occurances in a line

      :s/current-pattern/new-pattern/g\n

      Use % to specify the current buffer as the scope to change all matches

      :%s/current-pattern/new-pattern/g\n
      "},{"location":"neovim-basics/snippets/","title":"Snippets","text":"

      LuaSnip can use several different sources for snippets

      • VSCode JSON snippets (Friendly-snippets)
      • LSP style snippets
      LuaSnip Configuration

      Practicalli Neovim Config Redux includes the LuaSnip package which also adds friendly-snippets and cmp_luasnip.

        ; snippets\n  :L3MON4D3/LuaSnip \n  {:requires [:rafamadriz/friendly-snippets\n              :saadparwaiz1/cmp_luasnip]\n   :mod :lua-snip}\n
      Configure LSP snippet locations
      {\n  \"name\": \"practicalli-snippets\",\n  \"engines\": {\n    \"vscode\": \"^1.11.0\"\n  },\n  \"contributes\": {\n    \"snippets\": [\n      {\n        \"language\": [\n          \"markdown\",\n          \"global\",\n          \"all\"\n        ],\n        \"comment\": \"snippets accross several languages\",\n        \"path\": \"./global.json\"\n      },\n      {\n        \"language\": \n          \"markdown\",\n        \"path\": \"./markdown.json\"\n      }\n    ]\n  }\n}\n

      "},{"location":"neovim-basics/snippets/#snippet-definitions","title":"Snippet Definitions","text":"

      snippets directory contains snippet definitions, with a JSON file for each language, e.g. markdown.json

      Practicalli Neovim Config Redux contains several groups of snippet definitions

      • MkDocs format and icons (markdown.json VSCode syntax)

      Restart Neovim to load new defintions

      Snippets added to VSCode JSON snippets are only loaded when Neovim starts, so newly added snippets will only be available after Neovim is restarted.

      "},{"location":"neovim-basics/terminal/","title":"Terminal","text":"

      akinsho/toggleterm.nvim plugin provides a terminal session within Neovim, using a float, split or tab.

      AstroNvim

      SPC t for the Terminal sub-menu

      SPC t f opens a terminal in a floating window, useful for one-off commands

      SPC t h opens a terminal in a horizontal split, useful for a process that prints valuable feedback, e.g. a test runner in watch mode

      SPC t t opens a terminal in a tab, useful for background processes that do not need attendtion

      "},{"location":"neovim-basics/zen-mode/","title":"Focus Modes","text":"

      Focus on the code or text being created, without distractions

      zZ toggles Zen mode

      SPC z a ataraxis focus mode

      SPC z f focus current buffer

      SPC z n narrow to current buffer

      SPC z n remove status bar and window decorations

      v SPC z n narrow to selection

      "},{"location":"neovim-basics/zen-mode/#zen-mode","title":"Zen Mode","text":"

      Zen Mode distraction-free coding for Neovim

      Available via the Astrocommunity repository.

      Zen Mode configuration for AstroNvim

      .config/astronvim-config/plugins/community.lua
        { import = \"astrocommunity.editing-support.zen-mode-nvim\" },\n  {\n    \"folke/zen-mode.nvim\",\n    opts = {\n      -- override default configuration\n      -- https://github.com/folke/zen-mode.nvim#%EF%B8%8F-configuration\n      plugins = {\n        options = {\n          enabled = true,\n        },\n        kitty = {\n          enabled = true,\n          font = \"+4\", -- font size increment\n        },\n      },\n    },\n  },\n

      kitty configuration enables Zen Mode to resize kitty fonts.

      "},{"location":"neovim-basics/zen-mode/#true-zen","title":"True Zen","text":"

      true-zen.nvim clean and elegant distraction-free writing for NeoVim

      True Zen Mode configuration for AstroNvim

        {\n    \"Pocco81/true-zen.nvim\",\n    lazy = false,\n    opts = {\n      integrations = {\n        kitty = {\n          -- increment font size in Kitty.\n          enabled = true,\n          font = \"+4\",\n        },\n      },\n    },\n  },\n

      See kitty configuration to enable Zen Mode to resize kitty fonts.

      "},{"location":"neovim-basics/zen-mode/#kitty-configuration","title":"Kitty configuration","text":"

      Add allow_remote_control socket-only and listen_on unix:/tmp/kitty to the kitty config

      Kitty support for Zen Mode

      .config/kitty/kitty.config
      # ---------------------------------------------------------\n#  Neovim zen-mode-nvim\n#  - change the font size on kitty when in zen mode\nallow_remote_control socket-only\nlisten_on unix:/tmp/kitty\n# ---------------------------------------------------------\n
      "},{"location":"reference/lua-language/","title":"Lua","text":"

      Lua is the default language for Neovim configuration.

      "},{"location":"reference/lua-language/#learning-lua","title":"Learning Lua","text":"

      Neovim Lua introduction

      Lua.org - Programming in Lua (first edition)

      Codecademy - learn lua course

      "},{"location":"reference/lua-language/#reference","title":"Reference","text":"

      Lua.org 5.4 Reference Manual

      "},{"location":"reference/neovim/","title":"Neovim Reference","text":"
      • Language Providers
      • Key mappings
      "},{"location":"reference/neovim/language-providers/","title":"Language Providers","text":"

      Neovim delegates some features to language providers.

      :checkhealth command in Neovim shows if the binaries and tools required by each provider are available in the operating system.

      Resolve the issue with providers that generate a warning in the checkhealth report, following the ADVICE steps provided.

      "},{"location":"reference/neovim/language-providers/#disable-language-providers","title":"Disable Language Providers","text":"

      If a language is not used with Neovim, then its provider can be disabled. Details on how to disable a provider are included at the end of the ADVICE in the report section for that provider.

      Disable language providers in the init.lua configuration file

      init.lua
      -- Disable Language providers\nvim.g.loaded_node_provider = 0       --- (1)!\nvim.g.loaded_perl_provider = 0\nvim.g.loaded_python3_provider = 0\nvim.g.loaded_ruby_provider = 0\n
      1. Example configuration to disable providers is provided in the practicalli/neovim-config-redux configuration

      Ignore Language Provider warnings

      If the programming language is not used, there are no issues with using Neovim if the warnings are simply ignored

      "},{"location":"reference/neovim/standard-path/","title":"Neovim Standard Path","text":"

      View the standard paths used by Neovim using the help menu

      :help standard-path\n

      Output of command

      Standard Paths                  *standard-path*\n\nNvim stores configuration, data, and logs in standard locations. Plugins are\nstrongly encouraged to follow this pattern also. Use |stdpath()| to get the\npaths.\n\n                        *base-directories* *xdg*\nThe \"base\" (root) directories conform to the XDG Base Directory Specification.\nhttps://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\nThe $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_RUNTIME_DIR, and $XDG_STATE_HOME\nenvironment variables are used if defined, else default values (listed below)\nare used.\n\nCONFIG DIRECTORY (DEFAULT) ~\n                  *$XDG_CONFIG_HOME*            Nvim: stdpath(\"config\")\n    Unix:         ~/.config                   ~/.config/nvim\n    Windows:      ~/AppData/Local             ~/AppData/Local/nvim\n\nDATA DIRECTORY (DEFAULT) ~\n                  *$XDG_DATA_HOME*              Nvim: stdpath(\"data\")\n    Unix:         ~/.local/share              ~/.local/share/nvim\n    Windows:      ~/AppData/Local             ~/AppData/Local/nvim-data\n\nRUN DIRECTORY (DEFAULT) ~\n                  *$XDG_RUNTIME_DIR*            Nvim: stdpath(\"run\")\n    Unix:         /tmp/nvim.user/xxx          /tmp/nvim.user/xxx\n    Windows:      $TMP/nvim.user/xxx          $TMP/nvim.user/xxx\n\nSTATE DIRECTORY (DEFAULT) ~\n                  *$XDG_STATE_HOME*             Nvim: stdpath(\"state\")\n    Unix:         ~/.local/state              ~/.local/state/nvim\n    Windows:      ~/AppData/Local             ~/AppData/Local/nvim-data\n\nNote: Throughout the user manual these defaults are used as placeholders, e.g.\n\"~/.config\" is understood to mean \"$XDG_CONFIG_HOME or ~/.config\".\n\nLOG FILE                    *$NVIM_LOG_FILE* *E5430*\nBesides 'debug' and 'verbose', Nvim keeps a general log file for internal\ndebugging, plugins and RPC clients. >\n    :echo $NVIM_LOG_FILE\nBy default, the file is located at stdpath('log')/log unless that path\nis inaccessible or if $NVIM_LOG_FILE was set before |startup|.\n
      "},{"location":"reference/vim-style/","title":"Reference: Learn Vim-style Editing","text":"

      Learning vim-style multi-modal editing takes time and practice. Most importantly this approach can be learned in stages, as insert mode is the same editing experience as using most other editors.

      Start with the multi-modal concept and the basics of navigation. Then adopt more normal and visual mode actions, including motions

      "},{"location":"reference/vim-style/#getting-started","title":"Getting started","text":"

      :Tutor runs the intteractive tutorial built into neovim (:help tutor also runs the tutor)

      vim adventures isa simple online game to teach you the fundamentals of vim-style editing.

      "},{"location":"reference/vim-style/#navigation-and-searching","title":"Navigation and searching","text":"
      • moving the cursor
      • Motions
      • line numbers
      • jumping around a buffer
      • search and replace
      "},{"location":"reference/vim-style/#text-wrangling","title":"Text wrangling","text":"
      • iedit
      • text case
      "},{"location":"reference/vim-style/#clojure-editing","title":"Clojure Editing","text":"

      traversing expressions structural editing

      "},{"location":"reference/vim-style/case/","title":"Modifying text case","text":"

      Convert Characters and regioins to upper or lower case text.

      "},{"location":"reference/vim-style/case/#toggle-case-with-visual-select","title":"Toggle case with visual select","text":"

      v to visually select a character or use the vim motion keys to select a region

      U to uppercase current character or selected region

      u to lowercase current character or selected region

      ~ to toggle the case of the text in the selected region

      . will repeat the previous selection size and case toggle

      "},{"location":"reference/vim-style/case/#toggle-case-menu","title":"Toggle case menu","text":"

      Toggle the current character using vim motion keys, without needing to select a region.

      g ~ opens the toggle case menu

      TODO: Add screenshot of g ~ toggle case menu

      g ~ ~ uppercase current line (also works for RET and maybe other none-menu characters, but not SPC)

      "},{"location":"reference/vim-style/case/#cheatsheet","title":"Cheatsheet","text":"
      • ~ Changes the case of current character
      • guu Change current line from upper to lower.
      • gUU Change current LINE from lower to upper.
      • guw Change to end of current WORD from upper to lower.
      • guaw Change all of current WORD to lower.
      • gUw Change to end of current WORD from lower to upper.
      • gUaw Change all of current WORD to upper.
      • g~~ Invert case to entire line
      • g~w Invert case to current WORD
      • guG Change to lowercase until the end of document.
      • gU) Change until end of sentence to upper case
      • gu} Change to end of paragraph to lower case
      • gU5j Change 5 lines below to upper case
      • gu3k Change 3 lines above to lower case
      "},{"location":"reference/vim-style/g-menu/","title":"Evil G menu","text":"

      g` in normal mode opens a menu of convenient utilities. Practicalli uses this menu to comment existing lines, jumping to top or bottom of the buffer and changing text case.

      "},{"location":"reference/vim-style/g-menu/#comment-lines-and-regions","title":"Comment lines and regions","text":"

      g c c will comment the current line using the buffer major mode comment character(s). A prompt will ask if no comment character is set for the major mode.

      g c with a selected region will comment all lines with the major mode comment character(s)

      "},{"location":"reference/vim-style/g-menu/#jumping-around","title":"Jumping around","text":"

      g g jumps to the top of the buffer, g G to the bottom of the buffer

      g d to jump to the source code of a function definition, g D to open that in a different window.

      g f to jump to file name under cursor (if file exists).

      "},{"location":"reference/vim-style/g-menu/#changing-text-case","title":"Changing text case","text":"

      g u to change the current character or selection to lowercase, g U for uppercase.

      Toggle case with ~

      ~ will toggle the case of the current character or selected region.

      "},{"location":"reference/vim-style/key-binding-reference/","title":"Evil Keybinding Reference","text":"

      An alphabetically ordered reference of Vim-style key bindings.

      "},{"location":"reference/vim-style/key-binding-reference/#normal-mode-keybindings","title":"Normal mode keybindings","text":"

      Keybindings available in Evil normal mode and the resultant state they leave you in.

      Keybinding Description State @ execute keyboard macro (defined by q) Normal ' jump to mark (defined by m) Normal a append text after cursor Insert a append text after cursor Insert A append text to end of sentence Insert b move cursor back to start of previous word Normal B move cursor back to start of last word Normal c change (use with modifier) Insert C change rest of sentence Insert d delete (use with modifier) Normal D delete rest of sentence Normal e end of word Normal E end of word (seems same as above) Normal f find character forward (type character) Normal F find character backward (type character) Normal fd Escape (press almost together) Normal g go menu Normal G Jump to end of buffer Normal h move cursor left Normal H move cursor to top of buffer Normal i insert text Insert I insert text at start of sentence Insert j Move cursor down a line Normal J Join next line to end of current Normal k Move cursor up a line Normal K spacemacs/evil-smart-doc-lookup Normal l Move cursor right Normal L Move cursor to bottom of buffer Normal m Create marker (next character is marker name) Normal M Jump to middle of buffer Normal n Next search occurrence Normal N Previous search occurrence Normal o New line below Insert O New line above Insert p Paste (after / below cursor) Insert P Paste (before / above cursor) Insert q Record keyboard macro attached to given character Normal Q undefined Normal r Replace character under cursor Normal R Replace character state Replace s Substitute character Insert S Substitute current line Insert t Find character forward - cursor before character Normal T Find character backward - cursor after character Normal u Undo last change (C-R to redo) Normal U Undefined Normal v Visual select Visual V Visual select sentence Visual w Jump cursor to start of next word Normal W Jump cursor to start of next word Normal x Delete character under cursor Normal X Delete character backwards Normal y y Yank (copy) line Normal Y Yank (copy) line Normal z Menu - code folding / keyboard macros Normal"},{"location":"reference/vim-style/motions/","title":"Motions","text":"

      Todo

      Moving the cursor around by context rather than individual characters

      "},{"location":"reference/vim-style/moving-around/","title":"Moving around the cursor","text":"

      Scrolling is quite inefficient in most editors and moving (jumping) the curor around is far more effective.

      Using the h j k l as a common part of navigation provides consistency and keeps fingers on the most convienient part of the keyboard.

      "},{"location":"reference/vim-style/moving-around/#moving-by-charater","title":"Moving by charater","text":"

      h j k l keys move the cursor once character or can be used with numbers to move further.

      • h move left (often used to move up a path or tree, e.g. a directory path)
      • j move down
      • k move up
      • l move right (often used to move down a path or tree, e.g. a directory path)
      "},{"location":"reference/vim-style/moving-around/#moving-with-numbers","title":"Moving with numbers","text":"

      : followed by a number then one of h j k l keys will move the cursor that number in the director of the key.

      3j will move 3 lines down the buffer (or to the end of the fuffer if there are fewer lines remaining)

      Using Relative line numbers showws how far each line is from the current line. The practicalli/neovim-config sets :relativenames true in fnl/config/init.fnl.

      42l moves 42 charaters to the right

      moving by motions avoids the need to count characters

      "},{"location":"reference/vim-style/moving-around/#moving-around-the-buffer","title":"Moving around the buffer","text":"

      g g to jump to the top of the current buffer

      G to jump to the bottom of the buffer

      z z moves the current line and cursor to the middle of the window

      z t moves the current line and cursor to the top of the window

      z t moves the current line and cursor to the bottom of the window (or as far as the buffer will move in the window)

      "},{"location":"reference/vim-style/narrowing/","title":"Narrowing","text":"

      Narrowing to a region enables vim commands to be applied to a specific part of the current buffer, rather than the whole buffer.

      Common examples include - replacing local variables within a specific function (avoiding affecting other function definitions)

      "},{"location":"reference/vim-style/narrowing/#nrrwrgn-plugin","title":"NrrwRgn plugin","text":"

      [NrrwRgn plugin] is inspired by the Narrowing feature of Emacs and means to focus on a selected region while making the rest inaccessible.

      SPC n r opens a select region in a new split window. The original buffer is protected from changes.

      :w to write changes in the narrowed window to the original buffer

      "},{"location":"reference/vim-style/narrowing/#commands","title":"Commands","text":"

      :NR - Open the selected region in a new narrowed window :NW - Open the current visual window in a new narrowed window :WR - (In the narrowed window) write the changes back to the original buffer. :NRV - Open the narrowed window for the region that was last visually selected. :NUD - (In a unified diff) open the selected diff in 2 Narrowed windows :NRP - Mark a region for a Multi narrowed window :NRM - Create a new Multi narrowed window (after :NRP) - experimental! :NRS - Enable Syncing the buffer content back (default on) :NRN - Disable Syncing the buffer content back :NRL - Reselect the last selected region and open it again in a narrowed window

      Appending ! to most commands opens the narrowed part in the current window instead of a new window.

      :WR! closes the narrowed window in addition to writing to the original buffer.

      "},{"location":"reference/vim-style/narrowing/#documentation","title":"Documentation","text":"

      :help NarrowRegion to view the documetation on the NrrwRgn plug use

      "},{"location":"reference/vim-style/narrowing/#attention","title":"Attention","text":"

      :NRM is described as experimental by the project readme.

      "},{"location":"reference/vim-style/speaking-vim/","title":"Learning to speak Vim","text":"

      Neovim is easier to learn and gain much more benefit from if you learn to speak commands as sentences.

      First learn some verbs:

      • c change
      • d delete
      • g go,
      • v visual select
      • y yank (copy)

      Then use those verbs with some modifiers

      • ' mark
      • { } beginning/end of paragraph
      • 0 start of line
      • ^ first non white-space character of line
      • $ end of line
      • a around
      • f find (includes character)
      • i inside a range (e.g. word, paren,)
      • s surround
      • t till (move just before specified character)

      Then learn the text objects you can apply verbs and modifiers too

      • b block/parentheses
      • p paragraph,
      • s sentence
      • t tag e.g. html/xml
      • w word
      "},{"location":"reference/vim-style/speaking-vim/#examples-of-speaking-vim","title":"Examples of speaking Vim","text":"

      Practice speaking evil with these examples

      Keybinding Description c i s change inside current sentence (change the whole sentence) c i \" change inside double quotes c f ) change from cursor to next ) character c s ' \" change by the surrounding single quotes with double quotes c t X change till the character X (not including X) c /foo change until the first search result of \u2018foo\u2019 d d delete current line D delete current line from cursor onward d i w delete inside the current word (delete word) v t SPC visual select till the next Space character v s ] visually select and surround with [] without spaces v s [ as above with [ ] with spaces between parens and content g v go to last visual selection (select last visual selection) v a p visually select around current paragraph v i w S \" visually select, insert around current word, and surround with quotes y y yank (copy) current line y w yank (copy) current word y @ a yank (copy) to mark a (m a creates a mark called a)"},{"location":"reference/vim-style/vim-quick-reference/","title":"Neovim Quick Reference","text":"

      A reference of the most common keybindings available in Vim Normal mode. Spacemacs DOCUMENTATION key bindings section contains full details

      . repeats the last keybinding sequence used in Vim Normal mode or a change made within a complete Vim Insert session.

      "},{"location":"reference/vim-style/vim-quick-reference/#moving-around","title":"Moving around","text":"

      In Normal mode you can keep your fingers resting on the main row of your keyboard to move around.

      Key action j move cursor down one line k move cursor up one line l move cursor right one character h move cursor left one character

      In menus such as helm you can move around using Ctrl and these keybindings. So C-j will move the cursor down one item in a menu.

      "},{"location":"reference/vim-style/vim-quick-reference/#navigating-the-current-line","title":"Navigating the current line","text":"Key Action f to next character (you specify) t to just before the next character ; repeat f or t search w start of next word W start of next word, white space delimited e end of current word b start of previous word W end of next word, white space delimited * to next matching symbol name $ end of current line 0 start of current line ^ start of non-whitespace % jump to matching parens or next closed paren"},{"location":"reference/vim-style/vim-quick-reference/#navigating-the-current-buffer","title":"Navigating the current buffer","text":"Key action gg start of buffer G end of buffer H move cursor to head of buffer M move cursor to middle of buffer L move cursor to bottom line of buffer C-u jump up half a page C-d jump down half a page } move cursor forward by paragraph or block { move cursor backward by paragraph or block ma mark a line in a file with marker \"a\" `a after moving around, go back to the exact position of marker \"a\" 'a after moving around, go back to line of marker \"a\" :marks view all the marks '' go to the last place you were [{ jump back to the \"{\" at the beginning of the current code block C-o jump back to previous cursor location (evil-jump-backwards) C-i Go to newer position in jump list (opposite of C-o) : 4 go to line 4"},{"location":"reference/vim-style/vim-quick-reference/#text-editing","title":"Text Editing","text":"

      The following commands put you into the Evil Insert state

      Key Action i insert state at cursor I insert state at start of line a append - insert state after cursor A append - insert state at end of line o new line after cursor O new line before cursor"},{"location":"reference/vim-style/vim-quick-reference/#return-to-normal-state","title":"Return to Normal state","text":"

      Regularly switch back to normal state should become common practice. As soon as you finish typing some new text, it should become second nature to go back to normal state.

      ESC or press fd keys in extremely quick succession.

      fd shortcut for Esc

      Using f d together is low risk as if you dont get it right it will either add the characters or try find the next d character (as f moves to the next character). Keep trying this key combination as once in normal state you can use u to undo any f d characters inserted.

      "},{"location":"reference/vim-style/vim-quick-reference/#copy-cut-paste-undo-redo","title":"Copy, cut, paste, undo, redo","text":"

      v in Vim normal mode changes to Visual select mode. Use the navigation keys or any other movement keys to select text to copy or cut.

      Key Action y copy (yank) selection and add to kill ring x delete character at point and add to kill ring X delete character before point and add to kill ring p paste (put) u undo Ctrl-r redo

      Undo tips

      Undo will revert the last action in normal mode or all the changes you made in insert state

      "},{"location":"reference/vim-style/vim-quick-reference/#replace-and-changing-text","title":"Replace and changing text","text":"Key Action r replace the character under cursor R replace multiple characters until ESC cw change word from cursor to end 4 c w change 4 words v (select) c change region v (select) d delete region v i w c change current word v i d delete current word d w delete from cursor to end of word C change from cursor to end of line D , d $ delete from cursor to end of line"},{"location":"reference/vim-style/vim-quick-reference/#delete-commands","title":"Delete commands","text":"Key Action de delete to end of word, not including space dw delete to end of word, including space d$ delete to end of line dd delete the current line 4 d w delete 4 words 4 d $ delete 4 lines to end dt delete to a character (not including character) dab delete a whole block / expression dib delete contents of a block / expression cab change all the block / expression cib change inner block contents / expression yab yank all block / expression yib yank inner block contents / expression"},{"location":"reference/vim-style/vim-quick-reference/#repeat-commands","title":"Repeat commands","text":"Key Action . repeat last command again <number> <cmd> repeat command a number of times

      The . keybinding will repeat the last command in normal mode or the last text edit in insert mode.

      Type a number before a command and that command will run that number of times.

      Inserting a comment border

      Use the number repeat to create a border of 42 ; characters.

      Type 42 to repeat the command 42 times

      Press i for insert mode

      Press ; as the character to repeat insert

      Press ESC or fd to leave insert mode and insert all 42 ; characters

      "},{"location":"reference/vim-style/vim-quick-reference/#transposing-swap","title":"Transposing / swap","text":"Key Description x p transpose the current character with the next character"},{"location":"reference/vim-style/vim-quick-reference/#comments-works-for-all-major-modes","title":"Comments - works for all major modes","text":"

      g c c to comment out the current line

      g c to comment out the currently selected region

      To comment multiple lines you can use the repeat command style, especially useful if you are using relative line numbers.

      g c 3 j will comment the current line and the following two lines below. Comment in reverse using g c 3 k.

      In Visual state, v, select the lines you wish to comment and use g c to comment all the marked lines. Partially marked lines are not commented.

      "},{"location":"reference/vim-style/vim-quick-reference/#managing-files","title":"Managing Files","text":"

      Files in practicalli/neovim-config can be managed with Telescope plugin, although the neovim commands can also be used

      SPC p t toggles a visual file explorer on as a leftmost window, providing a further way to navigate files and directories.

      Key Description SPC f f find existing file (from current local root of neovim) SPC f / copy file - save current buffer with a new file name SPC f b browse files - Esc to run commands SPC f b Esc r change file name of current buffer

      Telescope file browser opens in Insert mode to allow typing filenames, to narrow the results in the Telescope popup.

      "},{"location":"reference/vim-style/vim-quick-reference/#telescope-browser-commands","title":"Telescope browser commands","text":"

      SPC f b opens telescope browser which allows commands to be run over the current file or directory.

      Esc swiches the Telescope popup to normal mode, allowing commands to be used

      • c create file / directory (any missing parts of a path are created)
      • r rename a file / directory
      • R replace

      TAB selects files and directories, allowing for commands (i.e. rename) to be done in batch mode (acting on all selected files / directories)

      "},{"location":"reference/vim-style/vim-quick-reference/#working-with-buffers","title":"Working with Buffers","text":"

      To work with files in Neovim they are loaded into a Buffer.

      Buffers are displayed in a window and you can change the window to show any of the current buffers.

      SPC b displays the buffer menu and the most common commands include:

      Key Command Description SPC b b :Telescope buffers List current buffers SPC b d :bdelete Kill current buffer SPC b n :bnext Switch to next buffer SPC b p :bprevious Switch to previous buffer SPC b a :ball Switch to previous buffer"},{"location":"reference/vim-style/vim-quick-reference/#quit-or-restart-emacs","title":"Quit or Restart Emacs","text":"

      I recommend using the Spacemacs menu from normal mode to quit / restart Spacemacs.

      Key Action SPC q a Quit Neovim (blocked if unsaved change in buffers) SPC q q Quit buffer (blocked if unsaved change in buffers) SPS q Q Force quit of Neovim"},{"location":"reference/vim-style/vim-quick-reference/#external-commands","title":"External commands","text":"

      run external commands using :! followed by a command. For example:

      :!ls - run the ls command

      "},{"location":"reference/vim-style/vim-tips-for-developers/","title":"Vim editing for Clojure developers","text":"

      Vim keybindings that drive Vim editing tools that are common for developers

      "},{"location":"reference/vim-style/vim-tips-for-developers/#comments-and-commenting-code","title":"Comments and Commenting code","text":"
      • g c c comment line
      • g c c comment line
      • v (select) g c comment region
      • g c 9 j comment 9 lines from current, downwards
      "},{"location":"reference/vim-style/vim-tips-for-developers/#simulated-structural-editing-with-surround","title":"Simulated structural editing with surround","text":"Keybinding Description v s ] surround with [characters] without spaces v s [ surround with [ characters ] without spaces c s ( [ change surrounding from ( to [ c i ( change in ( c a ( change \u201caround\u201d ( % jump forwards to next paren, further % toggles between open and close parens. x p transpose characters (cut current, paste after)"},{"location":"reference/vim-style/vim-tips-for-developers/#moving-around-quickly","title":"Moving around quickly","text":"

      f to jump forward to a given character on the current line. F to jump backwards.

      zt, zz, and zb to pull the current line to the top/middle/bottom of the screen.

      [number] G jump to line number or :22 to jump to line 22

      :7j to jump 7 lines down

      gf jump to file name under the cursor - try this in the summary.md file

      "},{"location":"reference/vim-style/vim-tips-for-developers/#selection-find-and-replace","title":"Selection, find and replace","text":"

      viw to visual-select in (within) the current word

      "},{"location":"reference/vim-style/vim-tips-for-developers/#source-code-and-configuration-files","title":"Source code and configuration files","text":"

      g Dopen definition in another window

      = (code-aware indenting) operator. Nice with the ap (a paragraph) text object.

      C-] Jump to definition of keyword under the cursor

      "},{"location":"reference/vim-style/vim-tips-for-developers/#code-folding","title":"code folding","text":"

      zc and zo are useful to close and open folds, which can be a nice way of focusing on certain pieces of code.

      "},{"location":"reference/vim-style/vim-tips-for-developers/#transposing-characters-and-sections","title":"Transposing characters and sections","text":"

      x p simple transpose of the current and next character

      M-t transpose words before and after cursor position

      {, } motions jump to next and previous empty lines. This motion makes it simple to rearrange paragraphs

      { d } will kill the paragraph (or multiple paragraphs)

      { will jump to the start of the previous paragraph

      p pastes the killed paragraph before the current paragraph

      > and < (indent and dedent) operators, useful with the aforementioned }/{ motions.

      / ## multi-replace with iedit and narrowing /

      "},{"location":"reference/vim-style/visual-select/","title":"Visual Select","text":"

      Select characters, words, lines and regions with visual select. The background changes color to visually indicate which text is selected

      v enters visual select mode and can be with objects such as word w and locations such as end of line $.

      o to expand from left side of selection and O to expand from the right side of a selection, using the hjkl navigation keys

      "},{"location":"reference/vim-style/visual-select/#visual-line-selection","title":"Visual Line selection","text":"

      V to select by lines, using j and k.

      "},{"location":"reference/vim-style/z-menu/","title":"Evil Z menu","text":"

      z in normal mode opens a menu of convenient utilities

      "},{"location":"reference/vim-style/z-menu/#folding-code-comments-and-other-content","title":"Folding code, comments and other content","text":"

      Code folding is very useful for hiding different levels of detail, for example you could hide everything but the function names in a namespace, showing just the API for that namespace.

      Comments and documentation can be folded to help you focus on a specific part of the content.

      Key Description z a toggle fold of code, comment, section, etc. z A toggle all folds z c close fold z f create fold z M close all folds z o open fold z O open fold recursive (capital o) z r fewer folds z R open all folds z x update folds

      See narrowing for a focused approach to editing.

      "},{"location":"reference/vim-style/z-menu/#scrolling","title":"Scrolling","text":"

      Jump the current line to the center, top or bottom of the buffer.

      Key Description z b scroll the current line to bottom of buffer z t scroll the current line to top of buffer z z scroll the current line to center of buffer"},{"location":"reference/vim-style/z-menu/#spelling","title":"Spelling","text":"

      z = with the cursor on a word shows a list of possible spelling and similar words.

      Select a word using its number in tye list to repace the word under the cursor, or q to quit the spelling list.

      Key Description z = spelling suggestions z g add word to spelling list z w mark word as misspelled"},{"location":"repl-driven-development/","title":"REPL Driven Development with Clojure","text":"

      Conjure provides the REPL driven development workflow for Clojure (and many other fun languages) and includes a built-in tutorial.

      • Vim style Editing
      • Starting a REPL (customise startup, add user namespace for dev tools)
      • Evaluating code
      • Structural Editing
      • Inspecting data - portal

      Start a REPL process in a terminal that includes nREPL server.

      Conjure will look for the ``.nrepl port in the root of the current project when a Clojure file is opened and connect to the REPL process via that nREPL server.

      "},{"location":"repl-driven-development/#clojure-cli-repl-process","title":"Clojure CLI REPL process","text":"

      Start a rich terminal UI for the Clojure REPL, using Rebel Readline

      clojure -M:repl/rebel\n

      This guide uses aliases from practicalli/clojure-deps-edn

      Or start a REPL that also includes the dev and test paths and libraries to hotload dependencies and refresh namespaces, for a reloaded REPL experience.

      clojure -M:env/dev:env/test:lib/reloaded:repl/rebel\n
      "},{"location":"repl-driven-development/#simplify-the-command-line","title":"Simplify the command line","text":"

      Add a Makefile to define common tasks to simplify and add consistency to working with Clojure across projects or shell script to simplify the commands used to call clojure to run common tasks

      repl:  ## Run Clojure REPL with rich terminal UI (Rebel Readline)\n    $(info --------- Run Rebel REPL ---------)\n    clojure -M:env/dev:env/test:repl/rebel\n\n\nrepl-reloaded:  ## Run Clojure REPL with hotload, reload and rich terminal UI (Rebel Readline)\n    $(info --------- Run Rebel REPL ---------)\n    clojure -M:env/dev:env/test:lib/reloaded:repl/rebel\n

      A Makefile can also include supporting commands, such as lint and format tools.

      # Run MegaLinter with custom configuration\nlint:\n    $(info --------- MegaLinter Runner ---------)\n    mega-linter-runner --flavor java --env 'MEGALINTER_CONFIG=.github/linters/mega-linter.yml'\n

      Practicalli Makefile

      practicalli/dotfiles/Makefile contains tasks for Clojure development, including running a REPL, preparing dependencies, building an uberjar, lint & format Clojure and configuration files.

      Docker related tasks to build, run and compose common images and containers are also included.

      "},{"location":"repl-driven-development/#references","title":"References","text":"
      • Which Clojure CLI execution option - M T X P - should be used
      • Using a Makefile to simplify Clojure development tasks
      "},{"location":"repl-driven-development/conjure/","title":"Conjure","text":"

      Conjure is the Clojure REPL client for Neovim. Code in source code buffers can be evaluated and show the results in-line, providing instant feedback on the behaviour of the code as it develops.

      Start a REPL on the command line in the root of a Clojure project. The REPL should also start an nREPL server for Conjure to connect too.

      Conjure will detect the nREPL server when a Clojure file is opended (.clj .edn .cljs .cljc).

      Practicalli Clojure CLI ConfigManual Alias definition

      Practicalli Clojure CLI config contains aliases to start a REPL process.

      :repl/rebel alias starts Rebel rich terminal UI REPL with nREPL server support.

      clojure -M:repl/rebel\n

      clojure -M:repl/basic starts a REPL with nREPL with a minimal REPL UI

      clojure -M:repl/headless starts a REPL with nREPL server but without a REPL prompt (to prevent accidental interaction via the command line)

      Add aliases to the user configuration for Clojure, e.g. XDG_HOME_CONFIG/clojure/deps.edn or HOME/.clojure/deps.edn

        ;; Interactive client REPL with nREPL server for Clojure Editor support\n  :repl/basic\n  {:extra-deps {nrepl/nrepl       {:mvn/version \"1.0.0\"}\n                cider/cider-nrepl {:mvn/version \"0.40.0\"}}\n   :main-opts  [\"--main\" \"nrepl.cmdline\"\n                \"--middleware\" \"[cider.nrepl/cider-middleware]\"\n                \"--interactive\"]}\n\n  ;; Headless REPL with nREPL server for Clojure Editor support\n  :repl/headless\n  {:extra-deps {nrepl/nrepl       {:mvn/version \"1.0.0\"}\n                cider/cider-nrepl {:mvn/version \"0.40.0\"}}\n   :main-opts  [\"--main\" \"nrepl.cmdline\"\n                \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}\n

      Practicalli Clojure CLI Config aliases

      Practicalli Clojure CLI config defines aliases for a wide range of community tools and libraries that extend the features of Clojure CLI

      "},{"location":"repl-driven-development/conjure/#fundamentals","title":"Fundamentals","text":"

      ,eb to evaluate all the Clojure source code in the current buffer, or ,ef to evaluate the current function. The result is displayed inline until the cursor moves. Open the REPL buffer to see larger results or a complete history.

      Conjure School interative tutorial

      :ConjureSchool runs an interactive tutorial in Neovim, walking through the essential Conjure commands and key bindings. Use the commands provided to move through the guide or j / k to scroll through the guide content.

      Practicalli Neovim config replaces some key bindings

      practicalli/neovim-config-redux replaces several key bindings to make them consistent with other Clojure editors

      "},{"location":"repl-driven-development/conjure/#evaluation","title":"Evaluation","text":"

      Clojure REPL workflow encourages code expressions to be evaluated as the are written, providing instant feedback to ensure expected results are returned (or learn the kind of results a function returns).

      ,eb - evaluate current buffer - used after first starting the REPL to load in a whole namespace and any required namespaces. Use to ensure all changes have been evaluated in the REPL (except those within a (comment ) form or otherwise commented)

      ,er - evaluate top-level expression (root), ignoring a surrounding (comment ) form to support the rich comments approach

      ,ee - evaluate expression (from start of current form) - especially useful for nested forms

      ,ei - interrupt evaluation (stop long running evaluations) - stop a long running evaluation

      ,ew - evaluate word (symbol) - inspect value of form - i.e. for def names

      ,e! - replace form with its result - helps understand a more complex function by replacing code with a specific value

      ,emf - evaluate marked form - mark forms regularly re-evaluted with mf (or any character with m) to avoid jumping to that form each time . A capital letter to mark form in a different namespace and evaluate from the current buffer.

      \"cp - paste contents of the register into buffer. The result of every evaluation is stored in a Neovim register as well as the log.

      "},{"location":"repl-driven-development/conjure/#repl-buffer-log","title":"REPL buffer (log)","text":"

      The Conjure REPL log shows the results of every evaluation for the current session.

      ,lt opens log in a new tab page (tab), ,ls in horizontal split, ,ls in vertical tab

      ,lq - close log window / tab page

      ,lr - soft REPL reset, leave window open

      ,lR - hard REPL reset, close window & delete buffer

      Inline evaluation over HUD log popup

      Practicalli Neovim configurations hide the HUD log popup that is otherwise shown when Conjure connects to the REPL process, i.e. vim.g[\"conjure#log#hud#enabled\"] = false

      In-line evaluation results are the main feedback approach used by Practicalli when evaluating code.

      Practicalli recommends using the REPL log when larger results are returned

      Portal data inspector can be sent evaluation history and provides rich visualisation and navigation tools to explore that history in detail.

      "},{"location":"repl-driven-development/conjure/#rich-comments","title":"Rich comments","text":"

      Rich comments are a useful way to contain experimental expressions, or expresisons only evaluated directly by a person developing the code (e.g. starting / stoping services, testing api calls, etc.)

      Expressions in rich comments are not included when evaluating the buffer or when expressions are evaluated via a namespace require.

      ,er to evaluate the top level form within the rich comment, without evaluating the comment expression itself.

      "},{"location":"repl-driven-development/language-server-protocol/","title":"Language Server Protocol (LSP)","text":"

      Using clojure-lsp server and Neovim Treesitter as an LSP client, code is statically analysed to provide auto-completion data, advanced editing actions such as refactor, live formatting, etc.

      Function help

      SPC l h or K displays help for the function under the cursor

      Repeat the key binding to move the cursor to the documentation popup window and use j k to scroll the documentation

      "},{"location":"repl-driven-development/language-server-protocol/#key-maps","title":"Key maps","text":"Practicalli AstroNvim ConfigPracticalli Neovim Config Redux
      • <leader>la code actions (popup with available actions)
      • <leader>ld hover diagnostics
      • <leader>lD search diagnostics
      • <leader>lf format buffer
      • <leader>lG search workspace symbols
      • <leader>lh function signature help
      • <leader>li information about the LSP client and running LSP servers
      • <leader>lI null-ls information (format & lint tools)
      • <leader>ll code lens refresh
      • <leader>lL code lens run
      • <leader>lr rename current symbol (namespace rename not supported it seems)
      • <leader>lR search references
      • <leader>ls search symbols
      • <leader>lS symbols outline
      • gd Go to definition
      • K Show documentations
      • <leader>ld Function declarations
      • <leader>lt Type Definitions
      • <leader>lh Signature Help
      • <leader>ln Rename
      • <leader>le Show line diagnostics
      • <leader>lq Show all diagnostics information
      • <leader>lf Auto format
      • <leader>lj Go to next diagnostic
      • <leader>lk Go to previous diagnostic
      • <leader>la Open code actions menu (Using telescope plugin interface)
      • <leader>la Open code actions menu for the selected text in VISUAL mode (Using telescope plugin interface)
      • <leader>lw Open workspace diagnostics list (Using telescope plugin interface)
      • <leader>lr Show all references list for item under the cursor (Using telescope plugin interface)
      • <leader>lr Show all implementations list for item under the cursor (Using telescope plugin interface)
      "},{"location":"repl-driven-development/language-server-protocol/#limitations-to-investigate","title":"Limitations to investigate","text":"
      • Neovim client does not seem to support namespace rename (AstroNvim)
      "},{"location":"repl-driven-development/testing/","title":"Unit tests and test runners","text":"

      Run unit tests from within Neovim, showing a summary of test results or a full test report (especially if there are failures)

      Or run Kaocka test runners in a terminal session, optionally in watch mode to re-run tests on every saved change.

      Practicalli sets Kaocha test runner as default

      practicalli/neovim-config-redux sets Kaocha as the default test runner

      Kaocha test runner set in Astrocommunity Clojure language pack

      Astrocommunity Clojure language pack
      {\n  \"Olical/conjure\",\n  -- load plugin on filetypes\n  ft = { \"clojure\" },\n  init = function()\n    vim.g[\"conjure#log#hud#width\"] = 1\n    vim.g[\"conjure#log#hud#enabled\"] = false\n    vim.g[\"conjure#log#hud#anchor\"] = \"SE\"\n    vim.g[\"conjure#log#botright\"] = true\n    vim.g[\"conjure#extract#context_header_lines\"] = 100\n    vim.g[\"conjure#eval#comment_prefix\"] = \";; \"\n    vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#enabled\"] = false\n    vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#hidden\"] = true\n    vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#cmd\"] = nil\n    vim.g[\"conjure#client#clojure#nrepl#eval#auto_require\"] = false\n    vim.g[\"conjure#client#clojure#nrepl#test#runner\"] = \"kaocha\"\n\n    vim.api.nvim_create_autocmd(\"BufNewFile\", {\n      group = vim.api.nvim_create_augroup(\"conjure_log_disable_lsp\", { clear = true }),\n      pattern = { \"conjure-log-*\" },\n      callback = function() vim.diagnostic.disable(0) end,\n      desc = \"Conjure Log disable LSP diagnostics\",\n    })\n
      "},{"location":"repl-driven-development/testing/#include-test-path","title":"Include test path","text":"

      Ensure the test directory is included in the classpath when starting a REPL. Use a project or user-level alias that includes an :extra-paths key that includes [\"test\"] path

      clojure -M:env/test:lib/reloaded:repl/rebel\n
      "},{"location":"repl-driven-development/testing/#conjure-test-runners","title":"Conjure Test runners","text":"

      , t n to run the tests for the current namespace

      "},{"location":"repl-driven-development/testing/#external-test-runner","title":"External test runner","text":"

      Open a terminal in Neovim or a separate terminal session to run start a test runner in watch mode. Tests run automatically when the code changes are saved

      Include the :env/test alias to include additional paths and dependencies for the test environment configuration, e.g. using additional libraries to run the tests such as mocking or human readable output.

      clojure -X:env/test:test/watch\n

      Refine the tests that are watched

      Start the watcher with focused or skiped tests by name or meta data (test selectors)

      Test selectors to run a sub-set of tests based on selector meta data added to deftest code

      clojure -X:test/watch :skip-meta :persistence\n

      TODO: check syntax of Kaocha test selectors

      Using Kaocha, specific test namespaces (or specific tests) can be run (or excluded)

      clojure -X:test/watch :namespace '[practicalli.server\"]\n

      TODO: check syntax of Kaocha namespace and specific tests

      "},{"location":"termux/","title":"NeoVim on Termux","text":"

      A smart phone or tablet and an external keyboard can make an excellent ultra-portable development environment, especially when travelling with limited space or restricted weight constraints.

      Termux can be installed using the F-Droid marketplace, as with installing any other Android app.

      Do not install Termux App from the Google Play store

      The Termux App in Google Play store is significantly out of date and will not work properly and probably not at all

      "},{"location":"termux/#keyboard","title":"Keyboard","text":"

      Atreus from Keyboardio is an excellent travel keyboard and was used to write most of the Practicalli Neovim configuration and Neovim book (especially during delays at airports)

      Model 100 from Keyboardio is used by Practicalli at the office

      "},{"location":"termux/#running-termux","title":"Running Termux","text":"

      Run Termux from the app launcher added as an android app by the F-Droid install.

      A help menu will show the basic command needed to work with packages.

      Software keys for Termux specific controls are shown. Keys can be toggled with the Volume Up + q key combination.

      Termux may not display in fullscreen when Android productivity mode, usually activated on tablets when physical keyboard or mouse is attached. Disabling productivity mode in the Android settings is recommended.

      "},{"location":"termux/clojure-development/","title":"Clojure development environment","text":"

      A comprehensive development environment for Clojure, supporting a REPL workflow and static analysis of code via Clojure Language Server Protocol.

      "},{"location":"termux/clojure-development/#java-host-platform","title":"Java host platform","text":"

      Install OpenJDK to host the Clojure REPL process and run packaged Clojure applications.

      Java 17 Long Term Support version is recommended as it is very stable, receives security updates and has the latest highly tested performance improvements.

      pkg install openjdk-17\n
      "},{"location":"termux/clojure-development/#install-clojure","title":"Install Clojure","text":"

      Clone practicalli/clojure-deps-edn to add a wide range of community tools to the Clojure CLI

      git clone git@github.com:practicalli/clojure-deps-edn.git ~/.config/clojure\n

      Use the Linux install with a prefix path pointing to Termux equivalent of /usr/local. Find the path using echo $PATH and remove bin from the end. Without the prefix Clojure will not install correctly

      curl -O https://download.clojure.org/install/linux-install-1.11.1.1149.sh\n\nchmod +x linux-install-1.11.1.1149.sh\n\n./linux-install-1.11.1.1149.sh --prefix /data/data/com.termux/files/usr/\n

      clojure binary is installed in the existing bin, lib and share directories in /data/..../usr/, placing that binary on the system execution path.

      Test by running a REPL session, for example with Rebel Readline

      clojure -M:repl/rebel\n

      optionally install rlwrap package if using the basic repl terminal UI

      "},{"location":"termux/clojure-development/#install-clojure-lsp","title":"Install Clojure LSP","text":"

      Visit clojure-lsp GitHub releases page and download the clojure-lsp file

      • visit the relases page in firefox and copy the link to the file.
      • use wget and paste the link to the file to download
      • make executable chmod 755 clojure-lsp
      • test locally ./clojure-lsp --version - should print clojure-lsp version and clj-kondo version
      • copy or move file to path mv clojure-lsp $PATH

      If the practicalli/clojure-lsp-config repository was cloned, move or link the clojure-lsp directory to ~/.config/clojure-lsp

      "},{"location":"termux/custom-shell/","title":"Customise shell","text":"

      Customising the shell is optional, although gives an enhanced experience.

      Zsh provides the richest command line experience, providing many advanced features over bash. Oh My Zsh is a community configuration that provides a simple way to configure Zsh features and also supports powerline10k terminal theme, providing context specific information and a more engaging visual experience.

      Oh My Zsh community configuration enhances the Zsh experience. Practicalli normally uses Prezto community configuration, unfortunately this did not work well on Termux.

      "},{"location":"termux/custom-shell/#install-zsh","title":"Install Zsh","text":"

      Install the zsh package using the Termux package manager

      pkg install zsh\n

      Start zsh, which will show a % character as the prompt

      zsh\n

      Set the shell to run zsh by default

      chsh -s zsh\n
      "},{"location":"termux/custom-shell/#install-oh-my-zsh","title":"Install Oh My Zsh","text":"

      Install Oh My Zsh via curl (or wget if preferred) in the .oh-my-zsh/ directory

      sh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\n
      "},{"location":"termux/custom-shell/#install-powerline10k","title":"Install Powerline10k","text":"

      Powerline10k is a visually appealing prompt with a setup script to visually choose the presentation of the prompt.

      git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k\n

      Edit ~/.zshrc and set the theme to ZSH_THEME=\"powerlevel10k/powerlevel10k\"

      nano ~/.zshrc\n

      Save the changes exit termux.

      Next time zsh is run, powerline10k setup script will run. If Meslo is not set as the terminal font, the setup script offers to install the font. Installing the font will restart Termux (without prompting for a restart).

      The powerline10k setup script provides a wizard to configure each part of the prompt.

      p10k configure command will manually run the powerline10k setup script.

      Prezto Zsh community configuration

      NOTE: previous attempts to use Prezto proved to have issues

      Clone prezto and its sub-modules into XDG_CONFIG_HOME/zsh which is typically ~/.config/zsh

      git clone --recursive https://github.com/sorin-ionescu/prezto.git \"${ZDOTDIR:-${XDG_CONFIG_HOME:-$HOME/.config}/zsh}/.zprezto\"\n

      Set the location of the Zsh configuration home with $ZDOTDIR, relative to the XDG locations

      export ZDOTDIR=\"${ZDOTDIR:=$XDG_CONFIG_HOME/zsh}\"\n

      Create a new Zsh configuration by copying/linking the Zsh configuration files provided:

      setopt EXTENDED_GLOB\nfor rcfile in \"${ZDOTDIR:-$HOME}\"/.zprezto/runcoms/^README.md(.N); do\n  ln -s \"$rcfile\" \"${ZDOTDIR:-$HOME}/.${rcfile:t}\"\ndone\n

      Practicalli Zsh configuration

      Clone practicalli/dotfiles and replace the symbolic links in $XDG_CONFIG_HOME/zsh with links to the respective Zsh configuration files in the cloned repository (or copy the files if you prefer)

      Copy or create a symbolic like for the .p10k configuration or skip this to create your own configuration when next startingzsh.

      Edit $XDG_CONFIG_HOME/.config/zsh/.zshenv and add the following lines to enable zsh to find the prezto configuration

      export XDG_CONFIG_HOME=\"${XDG_CONFIG_HOME:=$HOME/.config}\"\nexport ZDOTDIR=\"${ZDOTDIR:=$XDG_CONFIG_HOME/zsh}\"\n

      Create a symbolic link from $HOME/.zshenv to $XDG_CONFIG_HOME/.config/zsh/.zshenv (or to the .zshenv file from practicalli/dotfiles)

      ln -s $XDG_CONFIG_HOME/.config/zsh/.zshenv $HOME/.zshenv\n

      Check the configuration is working by loading the .zshenv configuration

      source \"$ZDOTDIR/.zshenv\"\n

      Using Oh My Bash

      If preferring Bash, then ohmybash provides a nice command line experience, showing completions clearer, nice themes that provide information.

      "},{"location":"termux/fdroid-install/","title":"Install Termux via the F-Droid App","text":"

      Visit the FDroid app website and download F-Droid, which saves an F-Droid.apk file. Android may display a security prompt stating the browser does not have permissions to install software. The popup should include a configure link that opens the Android settings to allow the browser to install software.

      F-Droid should now be installed and its icon added to the Android system alongside all other Android apps.

      Open the F-Droid App and allow it to update its repositories, to ensure the latest list of apps are shown.

      Search for the Termux application, clicking on the Termux name if more details are required.

      Select the Install button.

      When installing apps from F-Droid for the first time, a security prompt is show as F-Droid is an unknown source.

      Select Settings to open the Android settings and enable Allow from this source for the F-Droid app.

      A Termux App launcher will be added to the Android screen. Consider adding the Termux icon to the commonly used icons bar.

      "},{"location":"termux/fdroid-install/#termux-styling","title":"Termux Styling","text":"

      Styling is a Termux plugin that provides a visually richer experience. Styling contains beautiful color schemes and powerline-reaqdy fonts to customise the appearance of the terminal

      Install the package via F-Droid

      The styling menu is accessed via a long press on the Termux app screen, showing a More > Style option on the usual cut/copy/paste popup menu. Practicalli recommends FiraCode fonts.

      "},{"location":"termux/git/","title":"Git version control","text":"

      A Git client is used to version control projects and to clone projects and configuration from GitHub/GitLab. Practicalli maintains several editor configurations in shared repositories on GitHub

      • Install a Git Client (and optionally GitHub CLI)
      • [optionally] clone the practicalli/dotfiles repository for the Git config and global ignores
      • Configure an SSH key to access remote repositories (or Developer token if you cannot use SSH keys)
      "},{"location":"termux/git/#install-a-git-client-and-github-cli","title":"Install a git client and GitHub CLI","text":"
      pkg install git gh\n

      Clone the practicalli/dotfiles repository

      git clone https://github.com/practicalli/dotfiles projects/dotfiles\n

      Move or symbolically link the top-level directories to ~/.config, e.g. for the Git configuration:

      ln -s projects/dotfiles/git ~/.config/git\n

      Edit the .config/git/config and update the core.user, core.name and github or gitlab identities

      "},{"location":"termux/git/#create-ssh-key-for-remote-repository-access","title":"Create SSH Key for remote repository access","text":"

      Install the openssh package which contains the ssh-keygen command to generate a new public/private key combinations for use with GitHub SSH repository URLs

      pkg install openssh\n

      Generate a key using the email address of the GitHub or GitLab account

      ssh-keygen -t rsa -C name@domain.tld\n

      RET to confirm storing the keys in the default location.

      Usually a passphrase is recommended, however, termux does not seem to save a keyring to save the key passphrase using ssh-add. So the passphrase must be entered each time the key is used, unless a blank passphrase is used.

      Vist your GitHub account settings and create a new SSH key

      Use cat ~/.ssh/id_rsa.pub to show the public key contents. Press the screen to select and copy the public key to the clipboard.

      Paste the public key into the GitHub new key form.

      "},{"location":"termux/git/#optional-create-a-developer-token","title":"[optional] Create a developer token","text":"

      A developer token (or ssh key) is required to access GitHub {and far more secure over password}

      Should the android device become lost or compromised, the developer token can be deleted to protect the repositories from any malicious access. The developer token should be limited to the minimal access. The developer token does not give access to the GitHub or GitLab account.

      HTTPS URLs should be used with a developer token. git@git.com URLs are for SSH keys only.

      Visit GitHub / GitLab settings for your account

      Create a new developer token specifically for Termux

      Add a descriptive name for the token, based on the device Termuxc is runniung on, e.g. Termux Pixel2XL

      Check the public_repo and status repo scopes

      Generate button creates a new token.

      Copy the token using the copy icon.

      Edit the .config/git/config file and add a github section with the GitHub account name and token

      [github]\n    name = practicalli\n    token = ghp_************************************\n

      Consider using GitHub CLI to cache the developer token rather than write the token to the Git configuration file for greater security.

      "},{"location":"termux/neovim/","title":"Install neovim","text":"

      Neovim version 8 availabe as current package

      pkg install neovim\n
      "},{"location":"termux/neovim/#neovim-treesitter","title":"Neovim treesitter","text":"

      Treesitter provides excellent language syntax parsing and highlighting and is a very attractive feature of the recent neovim releases. Treesitter is a major attraction, bringing in a new audience for Neovim.

      The nvim-treesitter package is included in the practicalli/neovim-config-redux configuration.

      "},{"location":"termux/neovim/#c-compiler","title":"C Compiler","text":"

      Install C compiler for neovim-treesitter, to compile a parser for each specific programming language.

      pkg install clang\n

      gcc is not packaged for Termux, although there are guides to install gcc if preferred. clang has proved to be capable of creating the parsers used in the Practicalli configuration.

      "},{"location":"termux/neovim/#searching-files","title":"Searching files","text":"

      Telescope and other packages that involve searching for files recommend using ripgrep, a highly optomised tool for finding files on the operating system.

      pkg install ripgrep\n
      "},{"location":"termux/neovim/#optional-nodejs","title":"[optional] nodejs","text":"

      Optional. Only if node.js is required as a Neovim provider and JavaScript or ClojureScript development is to be done.

      pkg install nodejs\n
      "},{"location":"termux/setup/","title":"Termux Setup","text":"

      Launch Termux via its application icon. A black terminal screen will appear with a bash shell prompt.

      "},{"location":"termux/setup/#update-packages","title":"Update packages","text":"

      Check for new packages and update them all

      pkg upgrade -y\n

      If you wish to first check the packages that will be updated, use pkg --list-upgradable

      Select a specific region to minimise the number of mirrors checked during package upgrades, especially useful if on a limited data plan.

      termux-change-repo\n

      At time of writing, the Termux package on F-Droid was around 6 months old so there will be a number of packages that should be updated before any further installation steps are undertaken.

      "},{"location":"termux/setup/#configure-freedesktoporg-xdg-locations","title":"Configure Freedesktop.org XDG locations","text":"

      Edit the ~/.profile file, adding export directives to set the XDG locations:

      nano ~/.profile\n
      XDG locations
      # Common Free desktop.org locations\nexport XDG_CONFIG_HOME=$HOME/.config\nexport XDG_DATA_HOME=$HOME/.local/share\nexport XDG_STATE_HOME=$HOME/.local/state\nexport XDG_CACHE_HOME=$HOME/.cache\n\n# Set XDG location of Emacs Spacemacs configuration\nexport SPACEMACSDIR=\"$XDG_CONFIG_HOME/spacemacs\"\n

      Load the environment variables into the shell, or exit Termux and restart.

      Load .profile into shell
      source ~/.profile\n

      nano editor installed by default

      nano editor is used to edit the commands as the package is installed by default in termux. vim, neovim, emacs or any other Linux command line editor can be used if the package is first installed. Termux will list packages to install when trying to run a command that is from a package not yet installed.

      "},{"location":"termux/setup/#tools-to-download-binaries-and-configuration","title":"Tools to download binaries and configuration","text":"

      Many tools can be installed via the pkg tool, although specific Clojure tools and configuration require additional tools:

      • wget and curl - download tools not packaged, i.e. clojure-lsp binary
      • git - clone configuration files and projects (see Git version control section)
      • openssh - SSH service and tools to generate SSH keys
      pkg install curl wget git openssh\n

      Configure a Git Identify and SSH key to before committing and pushing changes, or cloning repositories using the SSH protocol. practicalli/dotfiles contains example configuration, ignore patterns and commit template for using Git.

      "},{"location":"termux/setup/#optional-configure-termux-settings","title":"[Optional] Configure Termux Settings","text":"

      Edit ~/.termux/termux.properties to configure the default settings for termux.

      nano ~/.termux/termux.properties\n

      Load termux.properties if values are changed (restarting Termux is not enough to load setting changes)

      termux-reload-settings\n

      The defaults are suitable for the majority of uses, although you may wish to consider:

      • fullscreen set to true to us the whole screen, hiding Android menu bars, etc.
      • hide-soft-keyboard-on-startup set to true if always using a physical keyboard
      • default-working-directory to save files user files and directories in an alternative location,

      If swiping from left edge of the screen is already taken, set key bindings for creating a new termux session, naming a session and switching between sessions. Alternatively, use byobu to create and switch between its tabs for multiple terminal sessions.

      "},{"location":"termux/setup/#set-color-scheme-and-font","title":"Set Color Scheme and Font","text":"

      The Termux:Styling plug provides menus for selecting terminal color scheme and font to use

      Press and hold on the Termux screen to show the context menu and select the Style menu. On smaller screens select More > Style

      If Termux:Styling plugin was not installed, a prompt will display asking if the plugin should be installed

      A menu appears with Choose Color and Choose Font

      Select Choose Color to select from the available list of colour schemes, e.g. Gruvbox Dark or Gruvbox Light

      ~/.termux/colors.properties file is added when selecting a colour scheme, defining Hex colors from the theme selected.

      Select Choose Font to select from the available fonts, e.g. FiraCode or Ubuntu

      ~/.termux/font.ttf font file is added when selecting a font.

      Termux:Styling uses NerdFonts for icons

      All fonts installed via Termux:Styling have been patched with NerdFonts, providing several thousand icons to use within the terminal prompt and Neovim itself (e.g. VimDevIcons).

      "},{"location":"termux/using-termux/","title":"Using Termux","text":"

      Start Termux app and a terminal prompt is shown, along with the standard Android software keyboard. An extended keyboard is provided with common key bindings for the command line interface (Tab, Esc, Ctrl, arrow keys, etc.).

      "},{"location":"termux/using-termux/#keyboards","title":"Keyboards","text":"

      Termux provides an extended keyboard with key combinations not possible with the Android software keyboard, i.e Ctrl-c, arrow keys, etc. TAB is especially useful for driving command and filename completion.

      Volume Up + q toggles the extended keyboard, so more screen is available when using a hardware keyboard.

      Connect a hardware keyboard for the best experience, e.g the Keyboard.io atreus is an excellent and highly portable mechanical keyboard. The software keyboard is automatically switched off when a hardware keyboard is connected, although the extended keyboard is still displayed by default.

      "},{"location":"termux/using-termux/#adjusting-font-size","title":"Adjusting Font size","text":"

      Pinch the screen inwards to zoom out making the text font smaller.

      Pinch the screen outwards to zoom in making the text font larger.

      "},{"location":"termux/using-termux/#termux-menus","title":"Termux menus","text":"

      Termux has three menus: A context menu, navigation drawer and Termux section of the Android notification.

      The context menu is shown by a long press anywhere on the terminal screen:

      • Select and Paste text to share text with other applications
      • Reset the terminal if it gets stuck or Hangup to exit the current terminal session
      • Style the terminal by selecting a font and a color scheme

      The navigation drawer is shown by swiping inwards from the left of the screen

      • list and select terminal sessions, set a name for a session with a long press
      • A button to toggle visibility of the touch keyboard.
      • A button to create new terminal sessions (long press for creating a named session or a fail-safe one).

      If gesture navigation is enabled in Android, hold the edge of the screen briefly before swiping to bring up the navigation drawer

      The Android notification menu contains a Termux section. Press the Termux section to show the current terminal session or expand the Termux section to exiting all running terminal sessions or aquire a wake lock to avoid Termux entering sleep mode. A wake lock allows server and other background processes to run reliably and to continue to receive notifications

      "},{"location":"termux/using-termux/#package-management","title":"Package management","text":"

      Termux provides a Linux command line experience, providing a wide range of Unix tools and development environments. Termux uses a Debian based system and packages are easily installed

      • apt install add tools and libraries to the Linux environment from the curated packages in the software center
      • apt update updates the list of packages fromhe software center
      • apt list --upgradable shows list of packages with new versions
      • apt upgrade install new versions of currently installed packages
      • apt-cache search --names-only - search for packages that include a specific pattern in their name.
      • apt-cache show - shows detail of the supplied package name, including a description

      pkg is an alias for apt, the advance package tool, although there seems little benefit to using pkg if familiar with apt (they are both 3 characters)

      "},{"location":"termux/using-termux/#byobu-terminal-tab-manager","title":"Byobu terminal tab manager","text":"

      Byobu is an alternative to Termux provides a single terminal prompt. Byobu provides multiple shell prompts, allowing individual Clojure tools and editors to be run from the Termux prompt simultaneously. Practicalli uses byobu to run Neovim, a Clojure REPL and unit test watcher in separate byobu tabs with the ability to add further tabs for other command line tools.

      pkg install byobu\n
      • F2 to create a new tab
      • F3 to select previous tab
      • F4 to select next tab

      byobu-enable command will configure the current shell to run byobu on startup. Test this is working by typing exit in Termux and start Termux app again. byobu-disable stops this behaviour and byobu will need to be run manually after starting Termux.

      Run the byobu-enable command again if zsh is configured after this step or if adding any other shell to Termux.

      "},{"location":"version-control/","title":"Version Control","text":"

      There are several ways to interact with Git version control, although Practicalli recommends Neogit interactive git client and Octo to manage GitHub issues and pull requests

      • lazygit terminal UI, embedded in Neovim (AstroNvim only)
      • Neogit git client similar to Emacs Magit, with Diffview integration
      • Octo for GitHub Issue and Pull Requests
      • Open in GitHub
      • Shell out to the command line, :!
      • Git commands in Neovim terminal buffer
      "},{"location":"version-control/#init-local-repository","title":"Init local repository","text":"AstroNvim

      Space t f opens floating terminal window in the current project directory root (or which ever directory Neovim was started from).

      Initialise a local git repository in the current directory.

      git init .\n

      "},{"location":"version-control/#stage-in-buffer","title":"Stage in buffer","text":"AstroNvimPracticalli Neovim Config Redux

      The current hunk or the whole buffer can be staged from the buffer using Git Signs, saving a trip to the Git Status buffer.

      Space g H stages the current hunk

      Space g S stages the current buffer

      Not supported.

      "},{"location":"version-control/#git-status","title":"Git Status","text":"AstroNvimPracticalli Neovim Config Redux

      SPC g g opens lazygit status, for minimal UI

      Space g s Space g n ++\"t\" opens neogit in a new tab for Magit style experience

      SPC g s opens Git Status tab, by running :Neogit

      "},{"location":"version-control/#github-integration","title":"GitHub integration","text":"

      Interact with the remote GitHub repository using Octo

      List issues from a specific repository

      :Octo issue list practicalli/neovim\n

      Create a pull request on a specific repository

      :Octo pr create practicalli/neovim\n
      "},{"location":"version-control/diff/","title":"Diff","text":"

      Compare differences between different files or between a file and its versions.

      :diffsplit filename Neovim command opens a split containing the selected filename, showing a diff comparision to the currently opened file

      file path completion helps select the correct file for comparison

      "},{"location":"version-control/diff/#git-diff","title":"Git Diff","text":"

      DiffView compares working space and staged changes side by side, or a diff for git merge conflicts.

      ++SPC++ g d or d in neogit status buffer (SPC g s) will open diffview in a new tab

      q to return to neogit status buffer

      • Green - added lines
      • Yellow - changed line
      • Red - deleted lines
      AstroNvimPracticalli Neovim Config

      Ctrl h / j / k / l to navigate between open splits

      SPC b toggles the sidebar buffer

      SPC w l and SPC w h to move cursor between diff buffer and sidebar buffer

      "},{"location":"version-control/lazygit/","title":"Lazygit version control","text":"Command Line or AstroNvim configuration

      Lazygit interface not provided by Practicalli Neovim Config Redux

      "},{"location":"version-control/lazygit/#requirements","title":"Requirements","text":"

      Install lazygit command line tool

      "},{"location":"version-control/lazygit/#open-lazygit","title":"Open Lazygit","text":"AstroNvimCommand Line

      SPC g g to open git status with lazygit in a popup window

      Change to the root directory of the git managed project.

      Run the lazygit rich terminal UI

      lazygit\n
      "},{"location":"version-control/lazygit/#use-lazygit","title":"Use Lazygit","text":"

      SPC to stage files or directories in the files section of the UI

      c for a simple commit message prompt in the lazygit UI

      C to create a commit message within the

      Define Editor for Git Commit Messages

      Set core.editor in the user Git configuration (i.e. .config/git/config) to the name of the editor to use for commit messages, e.g. nvim, emacsclient) shell title= git config --global core.editor = nvim Alternatively, use the VISUAL or EDITOR environment variable to the choice of editor

      "},{"location":"version-control/neogit/","title":"Neogit - interactive client for Git","text":"

      Neogit is an interactive git client that provides the core features of version control with Git. Neogit emulates many of the features found in magit.

      SPC g s to open :Neogit status buffer

      TAB toggles expansion of sections, files and hunks

      d provide a side-by-side view of changes

      q to quit Neogit and return to the previous tab

      Neovim is configured to use the magit style key bindings in practicalli/neovim-config-redux

      "},{"location":"version-control/neogit/#branching","title":"Branching","text":"

      b opens the branch menu,

      • b - checkout a branch
      • c - create a new branch
      • d - delete a branch, D deletes local and remote branch
      • l - checkout a remote branch and create a local tracking branch
      • m - rename an existing local branch
      • n - create a new branch
      "},{"location":"version-control/neogit/#staging-changes","title":"Staging changes","text":"

      s to stage change under cursor, either file or hunk. S to stage all changes

      u to unstage change under cursor, U to unstage all changes

      v to select lines to stage within a hunk using s or unstage with u

      "},{"location":"version-control/neogit/#commit","title":"Commit","text":"

      c for the commit menu

      c for a new commit, a to amend the latest commit, w to reword a commit message, e to add staged changes to existing commit

      A new commit or amend commit qill open a new window to write a commit message (using a git commit message template if defined)

      :wq to save a commit message and initiate the commit.

      :q! to cancel the commit from the commit message buffer.

      "},{"location":"version-control/neogit/#stashing-changes","title":"Stashing changes","text":"

      Z to open the stash menu

      z to stash the working copy and staged files (index)

      i to only stash the staged files (index)

      "},{"location":"version-control/neogit/#remote-changes","title":"Remote changes","text":"

      F to open the pull menu, p to pull changes (fetch and merge) from the remote repository, u t pull from the upstream repository, or e to specify the remote and branch names.

      P to open the push menu to open, -u to push to the current remote

      Confused when remote is not origin

      Use e option to push to elsewhere when the remote name is not set to origin. The e option will prompt for a remote name and branch.

      "},{"location":"version-control/neogit/#commit-history","title":"Commit history","text":"

      L l to view git commit history log

      RET on a log entry shows the commit details in a new window (split)

      q to close the commit details window

      "},{"location":"version-control/neogit/#modify-git-commit-history","title":"Modify Git commit history","text":"

      r opens the rebase menu

      "},{"location":"version-control/octo/","title":"Octo - GitHub issues and PRs","text":"

      List, create and edit issues and pull requests from Neovim with Octo package.

      Octo connects to GitHub via the GitHub CLI, using a developer token for authentication

      Neogit provides a Magit style client, creating commits, pull & push changes with remote repositories.

      "},{"location":"version-control/octo/#github-interaction","title":"GitHub interaction","text":"

      GitHub CLI

      Work with GitHub issues and Pull Requests from the comfort of Neovim.

      GitHub CLI to authentication to a GitHub account. Successful login creates a local developer token that is used by Octo to communicate with GitHub.

      gh auth login\n
      "},{"location":"version-control/octo/#octo-commands","title":"Octo commands","text":"

      Command line form: Octo <object> <action> [arguments] - Object, Action and Arguments commands

      List issues from current project (optionally add a specific repository)

      :Octo issue list practicalli/neovim\n

      The account/repository-name is required if Octo cannot find the repository

      Create a pull requests from current project

      :Octo pr create\n

      Add a comment to the current topic (issue/pr)

      :Octo comment add\n

      :Octo gist list\n

      Octo.nvim configuration options

      Octo.nvim configuration options

      "},{"location":"version-control/open-in-github/","title":"Open In GitHub","text":"

      Open a file under local version control in the GitHub web UI (browser window).

      Neovim

      :OpenInGHFile

      :OpenInGHRepo

      "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Neovim for Clojure development","text":"

      coding at the speed of thought

      Neovim is incredibly fast and efficient, so thoughts flow from brain to editor without unnecessary delay.

      Touch typing is fast, Neovim and multi-modal editing makes it even faster.

      Neovim is a highly extensible and powerful editor, supporting multi-modal editing and Vim-style sequential key bindings. Highly responsive and low resource use makes Neovim ideal for development on any computer or mobile device, e.g. tablet, smartphone.

      Neovim has a diverse set of plugins and Practicalli curated configurations use these plugins to provide a rich set of features for Clojure development and wider engineering tasks.

      Practicalli Neovim provides install & user guide focused on a simple, powerful and satisfying REPL Driven workflow for Clojure.

      "},{"location":"#quick-start","title":"Quick Start","text":"

      Install Clojure, Neovim and choose a community configuration

      • [Lua] AstroNvim
      • [Fennel] Practicalli Neovim Config Redux
      "},{"location":"#external-reverences","title":"External reverences","text":"

      Getting started with Neovim and Conjure

      Neovim user guide

      This Week In Neovim - community update

      Conjure install guide Conjuring Clojure in Vim

      "},{"location":"#navigate-the-book","title":"Navigate the book","text":"

      Use the mouse or built-in key bindings to navigate the pages of the book

      • P , , : go to previous page
      • N , . : go to next page

      Use the search box to quickly find a specific topic

      • F , S , / : open search dialog
      • Down , Up : select next / previous result
      • Esc , Tab : close search dialog
      • Enter : follow selected result
      "},{"location":"#sponsor-my-work","title":"Sponsor my work","text":"

      All sponsorship recieved is used to maintain and further develop the Practicalli series of books and videos, although most of the work is still done with my own time and cost.

      Thank you to Cognitect, Nubank and a wide range of other sponsors from the Clojure community for your continued support

      "},{"location":"#creative-commons-license","title":"Creative commons license","text":"This work is licensed under a Creative Commons Attribution 4.0 ShareAlike License (including images & style sheets)."},{"location":"api-tools/","title":"API Tools","text":"

      Astrocommunity proivdes plugins to support working with APIs and the JSON format

      • nvim-jqx
      • rest-nvim

      Included in Practicalli Astronvim Config

      Practicalli Astronvim Config includes nvim-jqx and rest.nvim plugins

      "},{"location":"api-tools/#inspect-json","title":"Inspect JSON","text":"

      Browse and preview json files in neovim.

      :JqxList prettify JSON and start the inspector

      JqxQuery to run complex jq commands

      jq binary required

      jq binary should be available on the command line as nvim-jqx runs jq queries internally

      nvim-jqx

      "},{"location":"api-tools/#call-apis","title":"Call APIs","text":"

      Space r r to run an http request under the cursor from within an *.http file.

      A fast Neovim http client written in Lua, providing a curl wrapper.

      "},{"location":"api-tools/#http-file","title":"http file","text":"

      Open a file with an *.http extension

      Write a call to an API, e.g. a call to a local server health care endpoint

      Call locally running API

      health-check.http
      GET http://localhost:8080/system-admin/status\n

      A new window opens with the result of the API call

      Result of API call with rest.nvim
      GET http://localhost:8080/system-admin/status\nCommand :curl -sSL --compressed -X 'GET' --data-raw '' 'http://localhost:8080/system-admin/status'\n#+END\nHTTP/1.1 200 OK\nContent-Type: application/json; charset=utf-8\nContent-Length: 66\nServer: http-kit\nDate: Mon, 10 Jul 2023 16:21:33 GMT\n\n#+RESPONSE\n{\"application\":\"practicalli hole-in-one Service\",\"status\":\"Alive\"}\n#+END\n

      The Content-Type can be explicitly set, especially useful when not using JSON

      API call returning EDN data

      GET http://localhost:8080/api/v1/scoreboard\naccept: application/edn\n

      rest.nvim test examples

      rest.nvim

      "},{"location":"assets/images/social/","title":"Social Cards","text":"

      Social Cards are visual previews of the website that are included when sending links via social media platforms.

      Material for MkDocs is configured to generate beautiful social cards automatically, using the colors, fonts and logos defined in mkdocs.yml

      Generated images are stored in this directory.

      "},{"location":"configuration/","title":"Neovim Configuration","text":"

      Practicalli Neovim covers the following configurations.

      • AstroNvim - thoughtful configuration, supports Neovim 0.9 onward, polished UI, many community extensions
      • Practicalli Neovim Config Redux - mnemonic key bindings, packer, telescope selector, written in Fennel
      "},{"location":"configuration/#multiple-configurations","title":"Multiple Configurations","text":"

      Install multiple configurations, e.g. AstroNvim, lazyvim, Nvchad, etc. in the $HOME/.config directory using unique directory names.

      Set NVIM_APPNAME to specific the configuration to use when running nvim.

      NVIM_APPNAME=astronvim nvim\n

      NVIM_APPNAME variable should be set to the directory name containing the configuration, relative to the .config directory.

      The configuration directory name is used to hold share, state and cache files for that specific configuration.

      Create shell aliases for each configuration. Optionalliy, define a terminal UI selection to choose a configuration.

      Shell AliasesTerminal UI Selector

      Create a Shell alias for each configuration that will be used, to avoid setting the NVIM_APPNAME variable each time.

      Define Shell Aliases to run each configuration

      alias astro=\"NVIM_APPNAME=astronvim nvim\"\nalias lazyvim=\"NVIM_APPNAME=lazyvim nvim\"\nalias practicalli-redux=\"NVIM_APPNAME=neovim-config-redux nvim\"\n

      Create an nvim configuration selector script, with items listing the directory name of each configuration

      Z Shell nvim-selector script

      .local/bin/nvim-selector
      function nvim-selector() {\n  items=(\"astronvim\" \"neovim-config-redux\" \"lazyvim\")\n  config=$(printf \"%s\\n\" \"${items[@]}\" | fzf --prompt=\"\ue62b Neovim Config \uf63d \" --height=~50% --layout=reverse --border --exit-0)\n  if [[ -z $config ]]; then\n    echo \"Nothing selected\"\n    return 0\n  elif [[ $config == \"default\" ]]; then\n    config=\"\"\n  fi\n  NVIM_APPNAME=$config nvim $@\n}\n
      "},{"location":"configuration/astronvim/","title":"AstroNvim","text":"

      AstroNvim is a community configuration with an engaging UI, using Lazy for plugin management (Neovim packages) and Mason for package management (LSP, DAP, format and lint tools)

      Practicalli AstroNvim Config is a user configuration that extends AstroNvim and imports packages from the AstroNvim Community.

      "},{"location":"configuration/astronvim/#prerequisits","title":"Prerequisits","text":"
      • Nerd Fonts version 3 - download a full font or only the symbols
      • fzf fuzzy finder (ubuntu archive)
      • gtu (Ubuntu package archive)
      • btm from GitHub repository releases
      • node.js (version 20) for Mason install of many LSP servers

      AstroNvim requires node.js

      AstroNvim uses Mason to install LSP servers, format and lint tools. Many LSP servers require node.js to install and function.

      Node.js install - Practicalli Engineering Playbook

      Kitty Terminal with Nerd Fonts

      Kitty Terminal - Practicalli Engineering Playbook provides examples of using Nerd Fonts or Nerd Font symbols with the Kitty terminal.

      "},{"location":"configuration/astronvim/#clone-astronvim","title":"Clone AstroNvim","text":"

      Clone AstroNvim repository to $HOME/.config/astronvim/

      git clone --depth 1 https://github.com/AstroNvim/AstroNvim ~/.config/astronvim\n

      $HOME/.config/nvim can be used instead if only ever using one configuration for Neovim.

      "},{"location":"configuration/astronvim/#clone-astronvim-user-config","title":"Clone AstroNvim user config","text":"

      AstroNvim provides a template repository to create a user configuration. The template includes AstroNvim Community configuration to make it easier to extend the feature of AstroNvim.

      Practicalli AstroNvim Config is a clone of the AstroNvim user config with additional configuration to support Clojure development.

      Practicalli AstroNvim ConfigAstroNvim User Config

      Clone the Practicalli AstroNvim config which provides a user configuration with Clojure support

      git clone http://github.com/practicalli/astronvim-config $HOME/.config/astronvim/lua/user\n

      Or clone to a separate directory and create a symbolic link

      git clone http://github.com/practicalli/astronvim-config $HOME/.config/astronvim-config && \\\nln -s $HOME/.config/astronvim-config/ $HOME/.config/astronvim/lua/user\n

      Create your own user configuration using the AstroNvim user configuration template repository.

      Create a repository from the AstroNvim/user_example repository template

      Clone the newly created repository into the existing AstroNvim configuration, in a user directory

      git clone git@github.com/<github-account>/<new-repository> $HOME/.config/astronvim/lua/user\n

      "},{"location":"configuration/astronvim/#configure-shell-alias","title":"Configure shell alias","text":"

      Create a shell alias that sets NVIM_APPNAME to the location of the AstroNvim community config

      Add alias to .bashrc for Bash shell or .zshenv for Zsh

      alias astro=\"NVIM_APPNAME=astronvim nvim\"\n

      Configure shell alias

      "},{"location":"configuration/astronvim/#post-install","title":"Post install","text":"

      Open a terminal and use the astro alias to run Neovim.

      astro\n

      NVIM_APPNAME=astronvim nvim to run Neovim with astronvim without setting a shell alias.

      Neovim will open and display the Lazy plugin manager UI, showing the progress of plugin installation. This should only happen on the first run.

      Unattended post install

      Plugins can be installed without running the Neovim editor UI

      nvim --headless -c 'autocmd User LazyDone quitall'\n
      "},{"location":"configuration/astronvim/#check-health","title":"Check Health","text":"

      Run the Neovim :checkhealth command to report on the general Neovim install and supporting tools

      "},{"location":"configuration/astronvim/#add-lsp-dap-lint-and-format-tools","title":"Add LSP DAP Lint and Format tools","text":"

      SPC p m to launch Mason which manages LSP servers, linters, filters ...

      "},{"location":"configuration/astronvim/#configure-format-rules","title":"Configure format rules","text":"

      The configuration files for each lint and format tool should be used by Neovim.

      Setting a different location for these files has proved challenging. plugin/null-ls.lua has a section to override its builtin configuration for each lint and format tool, however, in tests Practicalli was unable to succeffuly set a different location.

      "},{"location":"configuration/astronvim/config-design/","title":"\ud83d\udce6 Practicalli AstroNvim Config Design","text":"

      A guide to the AstroNvim Config user configuration created by Practicalli to support Clojure development.

      AstroCommunity used where possible

      Plugins and configuration is added vial AstroCommunity were possible, to minimise the code size and maintenance of the configuration

      "},{"location":"configuration/astronvim/config-design/#user-config-overview","title":"User Config overview","text":"

      core.lua is for tuning plugins shipped with astronvim config

      plugins/ for additional plugins organised logically. All .lua files are read from this directory

      • user.lua for general user defined plugins
      • clojure.lua adds Conjure and parinf, ensuring Clojure treesitter parser and Clojure LSP
      "},{"location":"configuration/astronvim/config-design/#clojure-support","title":"Clojure support","text":"

      The AstroCommunity provides a Clojure language pack that adds Conjure and nvim-parinfer, along with clojure Treesitter parser and clojure-lsp support.

      AstroCommunity PackManually add plugins

      Edit the plugins/community.lua file and import the Clojure pack. The \"AstroNvim/astrocommunity\", repository is already added to to the file.

      -- Packs\n-- Treesitter: clojure , Lsp: clojure-lsp, Lint/format:\n{ import = \"astrocommunity.pack.clojure\" },\n
      Override AstroCommunity Pack

      Create a plugins/clojure.lua file and add the AstroCommunity repository, Clojure pack and additional configuration to your own preferences

      Clojure configuration with user configration overrides

      return {\n  \"AstroNvim/astrocommunity\",\n  { import = \"astrocommunity.pack.clojure\" },\n  {\n    \"Olical/conjure\",\n    -- load plugin on filetypes\n    ft = { \"clojure\", \"fennel\" },\n    config = function()\n      -- HUD\n      -- Example: Set to `\"SE\"` and HUD width to `1.0` for full width HUD at bottom of screen\n      vim.g[\"conjure#log#hud#width\"] = 1 -- Width of HUD as percentage of the editor width, 0.0 and 1.0.\n      vim.g[\"conjure#log#hud#enabled\"] = false -- Display HUD\n      vim.g[\"conjure#log#hud#anchor\"] = \"SE\" -- Preferred corner position for the HUD\n      vim.g[\"conjure#log#botright\"] = true -- Open log at bottom or far right of editor\n      -- REPL\n      vim.g[\"conjure#extract#context_header_lines\"] = 100 -- Number of lines to check for `ns` form\n      vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#enabled\"] = false -- ;; Start \"auto-repl\" process, eg. babashka\n      vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#hidden\"] = true -- ;; Hide auto-repl buffer when triggered\n      vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#cmd\"] = nil -- ;; Command to start the auto-repl\n      -- ;; Automatically require namespace of new buffer or current buffer after connection\n      vim.g[\"conjure#client#clojure#nrepl#eval#auto_require\"] = false\n      -- Reloading code\n      -- Function to call on refresh (reloading) the log, namespace-qualified name of a zero-arity\n      -- vim.g[\"conjure#client#clojure#nrepl#refresh#after\"] = nil\n      -- The namespace-qualified name of a zero-arity function to call before reloading.\n      -- vim.g[\"conjure#client#clojure#nrepl#refresh#before\"] = nil\n      -- List of directories to scan. If no directories given, defaults to all directories on the classpath.\n      -- vim.g[\"conjure#client#clojure#nrepl#refresh#dirs\"] = nil\n      -- Testing\n      -- ;; Test runner called from the test key mappings\n      vim.g[\"conjure#client#clojure#nrepl#test#runner\"] = \"kaocha\"\n      -- Print raw test evaluation result, suppressing prefix for stdout lines `; (out)`\n      -- vim.g[\"conjure#client#clojure#nrepl#test#raw_out\"] = nil\n      -- Override string appended to the end of the test runner calls\n      -- vim.g[\"conjure#client#clojure#nrepl#test#call_suffix\"] = nil\n    end\n  },\n  {\n    \"gpanders/nvim-parinfer\",\n    ft = lisp_dialects,\n    config = function()\n      vim.g.parinfer_force_balance = true\n      vim.g.parinfer_comment_chars = \";;\"\n    end,\n  },\n}\n

      Add Conjure and parinfer plugin that will load when Clojure or Fennel file is opened.

      Clojure Packages in AstroNvim user configuration

      ```lua title=\".config/astronvim-config/plugins/clojure.lua\"\n-- Lazy Package manager configuration\nreturn {\n  {\n    \"Olical/conjure\",\n    -- load plugin on filetypes\n    ft = { \"clojure\", \"fennel\" },\n  },\n\n  {\n    \"gpanders/nvim-parinfer\",\n    ft = { \"clojure\", \"fennel\" },\n    config = function()\n      vim.g.parinfer_force_balance = true\n      vim.g.parinfer_comment_chars = \";;\"\n    end,\n  },\n}\n```\n

      Improve syntax highlighting by installing the Clojure parser for Treesitter.

      Treesitter Parser for clojure in AstroNvim user configuration

      .config/astronvim-config/plugins/treesitter.lua
      return {\n  \"nvim-treesitter/nvim-treesitter\",\n  opts = function(_, opts)\n    -- add more things to the ensure_installed table protecting against community packs modifying it\n    opts.ensure_installed = require(\"astronvim.utils\").list_insert_unique(opts.ensure_installed, {\n      -- \"lua\"\n    \"clojure\"\n    })\n  end,\n}\n

      Install Treesitter Clojure Parser manually

      :TSInstall clojure in Neovim will install the parser. A parser not included in the opts.ensure_installed configuration must be updated manually each time treesitter plugin is updated

      "},{"location":"configuration/astronvim/config-design/#clojure-mappings","title":"Clojure Mappings","text":"

      Conjure mappings are defined respective to a <localleader> value. Define a local leader in the AstroNvim user configuration, e.g. , and all Conjure mappings become available.

      AstroNvim 3.17.0 has localleader

      AstroNvim 3.17.0 release sets localleader to , so a separate setting is not required in the user configuration (unless a different localleader is preferred)

      Set localleader in user config

      options.lua in the user configuration provides a consistent way to set Neovim options.

      .config/astronvim-config/options.lua
      -- set vim options here (vim.<first_key>.<second_key> = value)\nreturn {\n  opt = {\n    -- set to true or false etc.\n    relativenumber = true, -- sets vim.opt.relativenumber\n    number = true,         -- sets vim.opt.number\n    spell = false,         -- sets vim.opt.spell\n    signcolumn = \"auto\",   -- sets vim.opt.signcolumn to auto\n    wrap = false,          -- sets vim.opt.wrap\n  },\n  g = {\n    mapleader = \" \",                 -- sets vim.g.mapleader\n    maplocalleader = \",\",            -- Set local leader key binding (supports Conjure key bindings)\n    autoformat_enabled = true,       -- enable or disable auto formatting at start (lsp.formatting.format_on_save must be enabled)\n    cmp_enabled = true,              -- enable completion at start\n    autopairs_enabled = true,        -- enable autopairs at start\n    diagnostics_mode = 3,            -- set the visibility of diagnostics in the UI (0=off, 1=only show in status line, 2=virtual text off, 3=all on)\n    icons_enabled = true,            -- disable icons in the UI (disable if no nerd font is available, requires :PackerSync after changing)\n    ui_notifications_enabled = true, -- disable notifications when toggling UI elements\n    VM_leader = \"gm\"                 -- Visual Multi Leader (multiple cursors)\n  },\n}\n
      "},{"location":"configuration/astronvim/config-design/#clojure-lsp","title":"Clojure LSP","text":"

      Clojure LSP support is enabled via the AstroCommunity Clojure pack.

      clojure_lsp can be added using Mason UI, SPC p m or in the plugins/mason.lua file

      Manual user config of clojure lsp server
      -- customize mason plugins\nreturn {\n  -- use mason-lspconfig to configure LSP installations\n  {\n    \"williamboman/mason-lspconfig.nvim\",\n    -- overrides `require(\"mason-lspconfig\").setup(...)`\n    opts = function(_, opts)\n      -- add more things to the ensure_installed table protecting against community packs modifying it\n      opts.ensure_installed = require(\"astronvim.utils\").list_insert_unique(opts.ensure_installed, {\n        -- \"clojure_lsp\",  -- provide by Clojure pack\n        \"marksman\", -- Markdown structure (also in markdown pack)\n        \"yamlls\",\n      })\n    end,\n  },\n}\n
      "},{"location":"configuration/astronvim/config-design/#snippets","title":"Snippets","text":"

      The AstroNvim user example includes a commented LuaSnip configuration

      .config/astronvim-config/plugins/core.lua
        -- {\n  --   \"L3MON4D3/LuaSnip\",\n  --   config = function(plugin, opts)\n  --     require \"plugins.configs.luasnip\" (plugin, opts)  -- include the default astronvim config that calls the setup call\n  --     -- add more custom luasnip configuration such as filetype extend or custom snippets\n  --     local luasnip = require \"luasnip\"\n  --     luasnip.filetype_extend(\"javascript\", { \"javascriptreact\" })\n  --   end,\n  -- },\n

      AstroNvim includes a Recipe for custom snippets

      return {\n  plugins = {\n    {\n      \"L3MON4D3/LuaSnip\",\n      config = function(plugin, opts)\n        require \"plugins.configs.luasnip\"(plugin, opts) -- include the default astronvim config that calls the setup call\n        require(\"luasnip.loaders.from_vscode\").lazy_load { paths = { \"./lua/user/snippets\" } } -- load snippets paths\n      end,\n    },\n  },\n}\n

      Practicalli AstroNvim Config combines the two examples to get

      AstroNvim config with custom VS Code style snippets

      .config/astronvim-config/plugins/core.lua
      {\n  \"L3MON4D3/LuaSnip\",\n  config = function(plugin, opts)\n    require \"plugins.configs.luasnip\" (plugin, opts) -- include the default astronvim config that calls the setup call\n    -- add more custom luasnip configuration such as filetype extend or custom snippets\n    require(\"luasnip.loaders.from_vscode\").lazy_load { paths = { \"./lua/user/snippets\" } } -- load snippets paths\n    local luasnip = require \"luasnip\"\n    luasnip.filetype_extend(\"javascript\", { \"javascriptreact\" })\nend,\n},\n
      "},{"location":"configuration/astronvim/config-design/#astronvim-community-packages","title":"AstroNvim Community packages","text":"

      AstroNvim Community provides a large number of packages currated by the community.

      Visit the AstroNvim Community repository on GitHub and browse the packages available.

      import each package of interest to the plugins/community.lua file in the AstroNvim user configuration.

      AstroNvim Community Packages in AstroNvim user configuration

      .config/astronvim-config/plugins/community.lua
      return {\n  -- Add the community repository of plugin specifications\n  \"AstroNvim/astrocommunity\",\n  -- Import each plugin from the Astro Community as required\n  { import = \"astrocommunity.editing-support.todo-comments\" },\n  { import = \"astrocommunity.git.neogit\" },\n  { import = \"astrocommunity.git.octo\" },\n  { import = \"astrocommunity.git.openingh\" },\n}\n

      AstroCommunity packs set up support for each language

      Language packs enabled in Practicalli AstroNvim Config

      .config/astronvim-config/plugin/community.lua
        -- Packs\n  -- Treesitter: dockerfile , Lsp: dockerls & docker_compose_language_service, Lint/format: hadolint\n  { import = \"astrocommunity.pack.docker\" },\n  -- Treesitter: json & jsonc, Lsp: jsonls, Lint/format: stylua\n  { import = \"astrocommunity.pack.json\" },\n  -- Treesitter: lua, Lsp: lua_ls, Lint/format: stylua\n  { import = \"astrocommunity.pack.lua\" },\n  -- Treesitter: markdown & markdown_inline, Lsp: marksman, Lint/format: prettierd\n  -- Pack disabled as prettierd too agressive with format\n  -- { import = \"astrocommunity.pack.markdown\" },\n  -- Treesitter: markdown & markdown_inline, Lsp: marksman, Lint/format: prettierd\n  { import = \"astrocommunity.pack.yaml\" },\n
      "},{"location":"configuration/astronvim/config-design/#themes","title":"Themes","text":"

      Themes are a collection of one or more colorschemes to affect the apperance of text, icons, highlights, etc.

      Themes supporting vim.opt.background can change between dark and light colorscheme (SPC u b UI > background in AstroNvim)

      SPC f t selector shows themes colorschemes, as long as the themes are configured to disable lazy loading

      The default astrodark theme is set via the colorscheme option in init.lua

      Everforest provides a good dark and light theme and supports the background option to toggle between each colorscheme.

      Practicalli AstroNvim Config - default theme

      colorscheme = \"everforest\",\n

      AstroCommunity themes

      Practicalli AstroNvim Config themes

      return {\n{\n\"AstroNvim/astrotheme\", -- default AstroNvim theme\nlazy = false,\n},\n  -- Add the community repository of plugin specifications\n  \"AstroNvim/astrocommunity\",\n  { import = \"astrocommunity.colorscheme.everforest\" },\n  {\n    \"sainnhe/everforest\",\n    lazy = false,\n  },\n  { import = \"astrocommunity.colorscheme.nightfox-nvim\" },\n  {\n    \"EdenEast/nightfox.nvim\",\n    lazy = false,\n  },\n  { import = \"astrocommunity.colorscheme.kanagawa-nvim\" },\n  {\n    \"rebelot/kanagawa.nvim\",\n    lazy = false,\n  },\n  { import = \"astrocommunity.colorscheme.github-nvim-theme\" }, -- no background support\n  {\n    \"projekt0n/github-nvim-theme\",\n    lazy = false,\n  },\n
      "},{"location":"configuration/astronvim/config-design/#configure-lazy-plugins","title":"Configure Lazy plugins","text":"

      Lazy.nvim Plugin specification

      "},{"location":"configuration/astronvim/config-design/#config-format-and-lint-tools","title":"Config Format and Lint tools","text":"

      Disable format on save when tools provide unexpected results

      SPC u f toggles if the respective format tool should run for the current buffer. SPC u F for all buffers of the current kind.

      init.lua lsp section can enable or disable format on save for specific file types.

      Mason is responsible for installing lint and format tools

      null-ls is responsible for running each tool and provides default configuration for code_actions, completion, diagnostics, formatting and hover.

      null-ls built-in configuration

      Override config file unconsistent

      The configuration file defined by -config-path does not always seem to be used when running astronvim. Quit and start Neovim again seems to use the configuration file.

      Override null-ls builtin configuration

      Specify configuration files to use that override the null-ls builtin configuration

      return {\n  \"jose-elias-alvarez/null-ls.nvim\",\n  opts = function(_, config)\n    -- config variable is the default configuration table for the setup function call\n    local null_ls = require \"null-ls\"\n    config.sources = {\n      null_ls.builtins.formatting.markdownlint.with {\n        -- pass arguments to modify/override the null-ls builtin configuration\n        extra_args = { \n          \"--config-path\", \n          vim.fn.expand(\"~/.config/astro-config/tool-config/markdownlint.yaml\") },\n      },\n    }\n    return config -- return final config table\n  end,\n}\n

      vim.fn.expand() reports luacheck error accessing undefined variable but seems to work regardless

      General configuration for LSP Servers .config/astronvim-config/init.lua
        lsp = {\n    -- customize lsp formatting options\n    formatting = {\n      -- control auto formatting on save\n      format_on_save = {\n        enabled = true,     -- format on save globally\n        allow_filetypes = { -- format on save for specified filetypes only\n          -- \"go\",\n        },\n        ignore_filetypes = { -- turn off format on save for specified filetypes\n          -- \"python\",\n        },\n      },\n      disabled = { -- switch off formatting capabilities for the listed language servers\n        -- turn off lua_ls formatting capability if you want to use StyLua to format your lua code\n        -- \"lua_ls\",\n        \"markdownlint\",\n      },\n      timeout_ms = 1000, -- default format timeout\n      -- filter = function(client) -- fully override the default formatting function\n      --   return true\n      -- end\n    },\n    -- enable servers that you already have installed without mason\n    servers = {\n      -- \"pyright\"\n    },\n  },\n
      "},{"location":"configuration/astronvim/config-design/#override-key-binding","title":"Override Key binding","text":"

      AstroNvim uses Lazy package manager to set keys for packages.

      Astrocommunity configuration defines a keys table that is used by Lazy.

      In the user configuration, return a function that sets key bindings to overide the keys table provided by astrocommunity

      Override Key bindings for vim highlighter .config/astronvim-config/plugins/community.lua
      {\n    \"vim-highlighter\",\n    keys = function() \n        return {\n            { \"<leader>nn\", \"<cmd>Hi><CR>\", desc = \"Next Recently Set Highlight\" },\n            { \"<leader>ng\", \"<cmd>Hi<<CR>\", desc = \"Previous Recently Set Highlight\" },\n            { \"<leader>n[\", \"<cmd>Hi{<CR>\", desc = \"Next Nearest Highlight\" },\n            { \"<leader>n]\", \"<cmd>Hi}<CR>\", desc = \"Previous Nearest Highlight\" },\n        }\n    end,\n}\n
      "},{"location":"configuration/astronvim/config-design/#plugin-key-binding","title":"Plugin Key binding","text":"

      Add key binding if a plugin is available wrapped in an if statement, when defining keys in a different place to adding the plugin, e.g whichkey mappings.lua

      if is_available \"plugin-name\" then\n  ,,,\nelse\n
      "},{"location":"configuration/practicalli/","title":"Neovim Config Redux","text":"

      practicalli/neovim-config-redux

      practicalli/neovim-config-redux is a Fennel based configuraion with a wide range of plugins and telescope extensions.

      Clone practicalli/neovim-config-redux or create a fork if intending to customise that configuration

      Multiple Neovim ConfigsSingle Neovim Configs
      git clone https://github.com/practicalli/neovim-config-redux.git ~/.config/neovim-config-redux\n
      git clone https://github.com/practicalli/neovim-config-redux.git ~/.config/nvim\n
      "},{"location":"configuration/practicalli/#screenshots","title":"Screenshots","text":"

      Dashboard using the startup plugin

      Mnemonic menu with which-key

      Telescope buffer selection

      Neogit Git client with diffview panel

      "},{"location":"configuration/practicalli/config-design/","title":"Config Design","text":"

      The overall design of the Practicalli Neovim Config Redux

      "},{"location":"configuration/practicalli/config-design/#initlua","title":"init.lua","text":"
      • bootstrap the aniseed package which compiles the Fennel configuration into Lua, which is then read by Neovim. Defines the entry point to the Fennel configuration as fnl/config/init.fnl
      • installs packer.nvim for package management
      • examples to disable language providers if programming language support is not required (node, perl, python3, ruby)
      "},{"location":"configuration/practicalli/config-design/#fnlconfiginitfnl","title":"fnl/config/init.fnl","text":"
      • load plugin configuration namespace config.plugin
      • load config.util namespace to streamline key binding definitions
      • set the leader key as space and local-leader as ,
      • define key bindings - uses config.util (mapping to be moved to their own namespace)
      • set global Neovim options
      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginfnl","title":"fnl/config/plugin.fnl","text":"

      Define plugins to add functionality to Neovim.

      use is a private function that searches the plugin configuration map for the keyword :mod and loads the associated namespace (namespace defined with a keyword with the same name)

      e.g. in the telescope plugin configuration :mod has a value of :telescope which will load the file fnl/config/plugin/telescope.fnl

        :nvim-telescope/telescope.nvim\n  {:requires [:nvim-lua/popup.nvim\n              :nvim-lua/plenary.nvim]\n   :mod :telescope}\n

      Packer downloads the nvim-telescope/telescope.nvim plugin and all the plugins in :requires section and search for the namespace telescope in file located in the following path fnl/config/plugin/telescope

      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginconjurefnl","title":"fnl/config/plugin/conjure.fnl","text":"

      The majority of default configuration settings are used for Conjure, with the exception of a few commonly used key bindings from Emacs CIDER & Spacemacs. The Heads Up Display (HUD) is also configured to be less intrusive, relying on mostly on inline results.

      Include the conjure and aniseed namespaces

      (module config.plugin.conjure\n  {autoload {nvim aniseed.nvim}})\n

      Configure keybindings to be closer to Spacemacs

      ;; Set e register for evaluation result\n(set nvim.g.conjure#eval#result_register :e)\n\n;; Evaluate root form (top level form) under the cursor\n;; Default: `\"er\"`\n(set nvim.g.conjure#mapping#eval_root_form \"ef\")\n\n;; Evaluate root form under the cursor & insert result as comment\n;; Default: `\"ecr\"`\n(set nvim.g.conjure#mapping#eval_comment_root_form \"e;\")\n\n;; Evaluate file loaded from disk\n;; Default: `\"ef\"`\n(set nvim.g.conjure#mapping#eval_file \"el\")\n

      Configure the HUD to be less intrusive.

      ;; Width of HUD as percentage of the editor width\n;; A float between 0.0 and 1.0.\n;; Default: `0.42`\n(set nvim.g.conjure#log#hud#width 1)\n\n;; Display HUD\n;; Default: `true`\n(set nvim.g.conjure#log#hud#enabled false)\n\n;; Preferred corner position for the HUD, over-ridden by HUD cursor detection\n;; Example: Set to `\"SE\"` and HUD width to `1.0` for full width HUD at bottom of screen\n;; Default: `\"NE\"`\n(set nvim.g.conjure#log#hud#anchor \"SE\")\n\n;; Open log at bottom or far right of editor, using full width or height\n;; Default: `false`\n(set nvim.g.conjure#log#botright true)\n

      Practicalli encourages header comments at the start of each file to describe the purpose of the namespace, so the Clojure ns lookup is extended

      ;; Number of lines to check for `ns` form, used for setting evaluation context\n;; `b:conjure#context` to override a specific buffer that isn't finding the context\n;; Default: `24`\n(set nvim.g.conjure#extract#context_header_lines 100)\n

      Disable the auto-repl as practicalli prefers manage repl connections themselves

      ;; Start \"auto-repl\" process, eg. babashka\n;; when Conjure unable to find candidate REPL process via to an existing nREPL connection\n;; Default: `true`\n(set nvim.g.conjure#client#clojure#nrepl#connection#auto_repl#enabled false)\n\n;; Hide auto-repl buffer when triggered, to avoid the need to interact with that buffer\n;; Default: `false`\n(set nvim.g.conjure#client#clojure#nrepl#connection#auto_repl#hidden true)\n\n;; Command to start the auto-repl\n;; Default: `\"bb nrepl-server localhost:8794\"`\n(set nvim.g.conjure#client#clojure#nrepl#connection#auto_repl#cmd nil)\n\n;; Print raw evaluation result, suppressing prefix for stdout lines `; (out)`\n;; Default: `false`\n(set nvim.g.conjure#client#clojure#nrepl#eval#raw_out true)\n\n;; Automatically require namespace of new buffer or current buffer after connection\n;; Ensures buffers are loaded, required code to compile and (re)loadable.\n;; Default: `true`\n(set nvim.g.conjure#client#clojure#nrepl#eval#auto_require false)\n

      Use lambdaisland/kaocha as the test runner rather, which has a fail fast feature which can be more effective when adding or changing functionality

      ;; Test runner called from the test key mappings\n;; Default: `\"clojure\"`\n(set nvim.g.conjure#client#clojure#nrepl#test#runner \"kaocha\")\n\n;; Print raw test evaluation result, suppressing prefix for stdout lines `; (out)`\n;; Default: `true`\n(set nvim.g.conjure#client#clojure#nrepl#test#raw_out true)\n
      "},{"location":"configuration/practicalli/config-design/#fnlconfigplugintelescopefnl","title":"fnl/config/plugin/telescope.fnl","text":"

      Settings like ignore node_modules and everything in .gitignore to be listed in the file finder.

      Defines a ripgrep command to set parameters for searching files

      Add --hidden to see all dotfiles (regardless of .gitignore patterns)

      Keymaps:

      • <leader>ff open the find files
      • <leader>fg open the fuzzy finder
      • <leader>fb open the find open buffer
      • <leader>fh open the nvim help fuzzy finder
      "},{"location":"configuration/practicalli/config-design/#fnlconfigplugintreesitterfnl","title":"fnl/config/plugin/treesitter.fnl","text":"

      Defines which language parsers and modules to use.

      • automatically use clojure, fennel and markdown parsers (and compile on first run of Neovim)
      • automatically update language parsers when nvim-treesitter plugin updated
      • enable highlight module
      • enable indent module
      (treesitter.setup\n  {:ensure_installed [\"clojure\" \"fennel\" \"markdown\"]\n   :sync_install true\n   :highlight {:enable true}\n   :indent    {:enable true}})\n
      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginlspconfigfnl","title":"fnl/config/plugin/lspconfig.fnl","text":"

      Language Server Protocol for static analysis of code, to provide common formatting, linting and refactoring tooling across all programming languages.

      Define which symbols to show for lsp diagnostics

      (defn define-signs\n  [prefix]\n  (let [error (.. prefix \"SignError\")\n        warn  (.. prefix \"SignWarn\")\n        info  (.. prefix \"SignInfo\")\n        hint  (.. prefix \"SignHint\")]\n  (vim.fn.sign_define error {:text \"\uf057\" :texthl error})\n  (vim.fn.sign_define warn  {:text \"\uf071\" :texthl warn})\n  (vim.fn.sign_define info  {:text \"\uf05a\" :texthl info})\n  (vim.fn.sign_define hint  {:text \"\uf059\" :texthl hint})))\n
      • features and server settings to enable/customize.
      • Handler defines features and how we want to render the server outputs.
      • Capabilities we link with our autocompletion plugin (nvim-cmp), to say to the lsp servers that we have this feature enabled.
      • On_Attach we customize our interaction with the LSP server, here we define the following keymaps:
      • configure all settings above in clojure-lsp server instance.
      "},{"location":"configuration/practicalli/config-design/#fnlconfigplugincmpfnl","title":"fnl/config/plugin/cmp.fnl","text":"

      Configure sources to show in the autocomple menu (i.e. conjure, lsp, buffer) and key bindings to navigate the autocomplete popup menu.

      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginthemefnl","title":"fnl/config/plugin/theme.fnl","text":"

      Add the Neovim GitHub theme which gives 3 dark and 3 light themes to choose from. Individual colors and styles can be configured to change specific parts of the theme.

      The light theme is used by default, with a custom softer background colour that is slightly red-shifted.

      Options are specified in the theme.setup function, where the option names are keywords and the values are strings, boolean or hash-map of more option keywords and values.

      (theme.setup {:theme_style \"light\"\n              :colors {:bg \"#f8f2e6\"}\n              :comment_style \"italic\"})\n

      The colors (Hex values) for each theme are in the github-nvim-theme/lua/github-theme/palette with the overal theme definition in github-nvim-theme/lua/github-theme/theme.lua

      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginsexpfnl","title":"fnl/config/plugin/sexp.fnl","text":"

      Settings for vim-sexp like enabling it for another lisp languages like Fennel and Jannet

      "},{"location":"configuration/practicalli/config-design/#fnlconfigpluginlualinefnl","title":"fnl/config/plugin/lualine.fnl","text":"

      Configure the status line (lualine) that shows at the bottom of Neovim, defining colors and elements that appear on that line.

      The Neovim GitHub theme includes definitions to set the look of the status line.

      "},{"location":"configuration/practicalli/packer/","title":"Package Manager","text":"

      Packer is a use-package inspired package management for Neovim.

      Packer is used as the package manager in this guide as it is built on native Neovim packages and supports Luarocks dependencies, use the :help packages command in Neovim for more details.

      Packer is written in Lua and is installed via the init.lua configuration file, although Practicalli Neovim configuration uses Fennel to configure each package added by Packer.

      "},{"location":"configuration/practicalli/packer/#install","title":"Install","text":"

      init.lua is the entry point to the configuration and is the only part that is written in Lua language.

      The configuration bootstraps the Packer package manager and installs the Aniseed compiler required to process the fennel configuration.

      Aniseed compiles and loads fnl/config/init.fnl and all the required namespaces in that file.

      Packer will process the use form in fnl/config/plugin.fnl and install all the packages defined in that form, along with any package specific configuration defined in that package {:mod :namespace-name} file.

      local execute = vim.api.nvim_command\nlocal fn = vim.fn\n\nlocal pack_path = fn.stdpath(\"data\") .. \"/site/pack\"\nlocal fmt = string.format\n\nfunction ensure (user, repo)\n  -- Ensures a given github.com/USER/REPO is cloned in the pack/packer/start directory.\n  local install_path = fmt(\"%s/packer/start/%s\", pack_path, repo, repo)\n  if fn.empty(fn.glob(install_path)) > 0 then\n    execute(fmt(\"!git clone https://github.com/%s/%s %s\", user, repo, install_path))\n    execute(fmt(\"packadd %s\", repo))\n  end\nend\n\n-- Bootstrap essential plugins required for installing and loading the rest.\nensure(\"wbthomason\", \"packer.nvim\")\nensure(\"Olical\", \"aniseed\")\n\n-- Enable Aniseed's automatic compilation and loading of Fennel source code.\nvim.g[\"aniseed#env\"] = {\n  module = \"config.init\",\n  compile = true\n}\n
      "},{"location":"configuration/practicalli/packer/#packages","title":"Packages","text":"

      Neovim packages add extra functionality to Neovim, e.g. conjure package provides an excellent Clojure REPL experience (and supports several other languages too).

      See the packages section for details of the packages used and a breakdown of their configuration.

      "},{"location":"configuration/practicalli/packages/","title":"Add Neovim Packages","text":"

      Evolving Packages in Practicalli config

      Check the practicalli/neovim-config-reduct configuration. Many packages have been added to the configuration and fnl/config/package.fnl is the most up to date list of packages currently used.

      List of packages and their purpose

      Package Description conjure Clojure REPL Driven Development (and other language REPLs) sexp Structured Editing newpaper theme Clean and simple UI & colour scheme, aimed at readably lualine Fast and configurable statusline nvim-treesitter Parse code highly efficiently, client for LSP servers telescope Completion tool, e.g. select files, buffers tabs, packages, etc nvim-tree Visual file manager - open, create, delete, etc. files & directories neogit Magit style visual Git client Octo Git Issues and Pull Requests gitsigns Show diff changes in buffer gutter and status line

      Any specific package configuration & key bindings (on sub page if significant content)

      "},{"location":"configuration/practicalli/packages/#package-selection-criteria","title":"Package selection criteria","text":"

      Packages are more likely to be adopted if:

      • provide valuable (or fun) features
      • work reliably, without generating errors
      • do not conflict with other valuable packages in this configuration
      • written in fennel or lua to aid maintenance
      • provides a setup or config function for setting package options
      • use features provided by Neovim (e.g treesitter)
      • are well documented
      • are easy to configure
      • are easy to use
      • work well with themes (where relevant)
      "},{"location":"configuration/practicalli/packages/#package-updates","title":"Package Updates","text":"

      This Week In Neovim - community update

      "},{"location":"configuration/practicalli/packages/lualine/","title":"Lualine - modeline theme","text":"

      nvim-lualine/lualine.nvim is a fast and configurable statusline for neovim

      Example status line: evil_lualine

      "},{"location":"configuration/practicalli/packages/lualine/#lualine-configuration-in-fennel","title":"Lualine configuration in Fennel","text":"

      nvim/fnl/config/plugin/lualine.fnl

      (module config.plugin.lualine\n  {autoload {core aniseed.core\n             lualine lualine\n             lsp config.plugin.lspconfig}})\n\n(defn lsp_connection []\n  (if (vim.tbl_isempty (vim.lsp.buf_get_clients 0)) \"\uf096\" \"\uf0c8\"))\n\n(def github-lua-theme\n  (core.assoc\n    (require :lualine.themes.auto)\n    :inactive {:a {:bg \"#19181e\" :fg \"#a4a3a6\"}\n               :b {:bg \"#19181e\" :fg \"#a4a3a6\"}\n               :c {:bg \"#19181e\" :fg \"#a4a3a6\"}}\n    :normal {:a {:bg \"#131217\" :fg \"#24292e\"}\n             :b {:bg \"#131217\" :fg \"#3b8eea\"}\n             :c {:bg \"#19181e\" :fg \"#d1d5da\"}}\n    :command {:a {:bg \"#131217\" :fg \"#24292e\"}\n              :b {:bg \"#131217\" :fg \"#ccbed8\"}\n              :c {:bg \"#19181e\" :fg \"#d1d5da\"}}\n    :visual {:a {:bg \"#131217\" :fg \"#24292e\"}\n             :b {:bg \"#131217\" :fg \"#ced4b1\"}\n             :c {:bg \"#19181e\" :fg \"#d1d5da\"}}\n    :replace {:a {:bg \"#131217\" :fg \"#24292e\"}\n              :b {:bg \"#131217\" :fg \"#d1b6bd\"}\n              :c {:bg \"#19181e\" :fg \"#d1d5da\"}}\n    :insert {:a {:bg \"#131217\" :fg \"#24292e\"}\n             :b {:bg \"#131217\" :fg \"#a8d1c9\"}\n             :c {:bg \"#19181e\" :fg \"#d1d5da\"}}))\n\n(lualine.setup\n  {:options {:theme github-lua-theme\n             :icons_enabled true\n             :section_separators [\"\" \"\"]\n             :component_separators [\"\uf44a\" \"\uf438\"]}\n   :sections {:lualine_a []\n              :lualine_b [[:mode {:upper true}]]\n              :lualine_c [[\"FugitiveHead\"]\n                          [:filename {:filestatus true\n                                      :path 1}]]\n              :lualine_x [[:diagnostics {:sections [:error\n                                                    :warn\n                                                    :info\n                                                    :hint]\n                                         :sources [:nvim_lsp]}]\n                          [lsp_connection]\n                          :location\n                          :filetype]\n              :lualine_y [:encoding]\n              :lualine_z []}\n   :inactive_sections {:lualine_a []\n                       :lualine_b []\n                       :lualine_c [[:filename {:filestatus true\n                                               :path 1}]]\n                       :lualine_x []\n                       :lualine_y []\n                       :lualine_z []}})\n
      "},{"location":"configuration/practicalli/packages/nvim-treesitter/","title":"Nvim Treesitter","text":"

      Treesitter provides language specific parsing, highlight and indent features and so is a fundamental plugin to use with Neovim.

      clojure, fennel, markdown and org parsers are automatically installed in the practicalli/neovim-config-redux configuration.

      • :TSInstallInfo lists language parsers and install status
      • :TSUpdate {language} to update a parser to the latest compatible version (specified in nvim-treesitter lockfile.json).
      • :TSInstall {language} compiles and installs a parser for the given language.
      • :TSUpdateSync to update all parsers to the latest available versions
      "},{"location":"configuration/practicalli/packages/nvim-treesitter/#nvim-treesitter-configuration","title":"nvim-treesitter configuration","text":"

      clojure, fennel, markdown and org parsers are automatically installed if not already available.

      :sync_install true automatically updates the parsers when the nvim-treesitter plugin is updated. Treesitter and its parsers are actively developed, so its important to ensure parsers are kept up to date. This is the equivalent of manually running :TSUpdateSync.

      Parser highlight and indent modules are enabled by default

      In fnl/config/plugin/treesitter.fnl

      (module config.plugin.treesitter\n  {autoload {treesitter nvim-treesitter.configs}})\n\n(treesitter.setup\n  {:ensure_installed [\"clojure\" \"fennel\" \"markdown\" \"org\"]\n   :sync_install true\n   :highlight {:enable true}\n   :indent    {:enable true}})\n
      "},{"location":"configuration/practicalli/packages/nvim-treesitter/#manually-install-parsers","title":"Manually Install Parsers","text":"

      nvim-treesitter provides the TSInstall command to generate a parser for a specific language, assuming that language is supported.

      A compiler (gcc, clang, etc) should be installed in the operating system on which nvim is running

      :TSInstall {language}\n

      TAB completion lists the available language parsers, TAB and S-TAB to navigate the auto-completion popup.

      "},{"location":"install/","title":"Install Overview","text":"

      Practicalli Neovim provides a feature rich configuration for Neovim and all the tools required for effective Clojure development (and other Lisp dialects too).

      • Clojure tooling and a Java SDK (Java Virtual Machine)
      • Neovim 0.9.x or nightly build
      • Neovim package manager and packages
      • NerdFonts for icon support in themes and status line

      Neovim 0.9.x latest stable release

      Content and configuration in this book has been tested against Neovim 0.9.x over the summer of 2023

      "},{"location":"install/#install-summary","title":"Install summary","text":"

      If you are familiar with most of the tools required, then the quick start list below provides an ultra-terse version on how to get started with Neovim and Clojure development.

      • Install Neovim 0.9.x or greater
        • Linux AppImage
        • brew install --HEAD neovim for Homebrew install of development version
      • Install supporting tools
        • tar & curl and a C compiler, e.g. gcc for Linux or clang for android/termix (required by nvim-treesitter)
        • ripgrep & fd to search for files (used by telescope)
        • luarocks for LSP support in AstroNvim
      • Clone Neovim Config
      • Run nvim in a terminal and ignore warnings, press RTN
        • SPC P i or :PackerInstall command in Neovim to install packages
      • Install Clojure CLI and supporting tools
      • Clone / fork practicalli/clojure-deps-edn or add an alias with the required config to use nrepl and cider-nrepl
      • Run a Clojure REPL process - in a terminal session with nREPL, e.g. using one of the REPL aliases from practicalli/clojure-deps-edn
        • clojure -M:repl/rebel for a rich REPL UI with auto-completion & docs
        • clojure -M:repl/headless - headless REPL process when working exclusively in a Clojure connected editor
      • Open a Clojure file in Neovim - Conjure will automatically connect
      "},{"location":"install/#next-steps","title":"Next Steps","text":"

      Learn how to use Neovim and how to use Conjure for REPL driven development

      "},{"location":"install/clojure/","title":"Install Clojure","text":"

      A rich Clojure REPL workflow is provided by the Conjure package, which works with Clojure CLI and Leiningen projects, assuming the respective tool is installed.

      Clojure LSP is highly recommended and packages to use an installed clojure-lsp tool are in the practicalli/neovim-config-redux configuration

      "},{"location":"install/clojure/#clojure-cli","title":"Clojure CLI","text":"

      Practicalli Clojure install guide

      Clojure CLI provides a way to run Clojure code, packaged Clojure (jar) and run a Clojure REPL.

      Practicalli Clojure install guide details prerequisites, Clojure install options and supporting tools for an enhanced developer workflow.

      Visit the Clojure Getting Started guide for the Clojure CLI or to check the latest release version.

      Practicalli Clojure CLI Config provides a wide range of community tools that extend the features of Clojure CLI, creating a rich development environment for use across all projects.

      Aliases are required for many examples

      Without Practicalli Clojure CLI Config many commands provided in this book are not available unless similar alias definitions are added to a either a project or user level deps.edn configuration.

      "},{"location":"install/clojure/#language-server-protocol","title":"Language Server Protocol","text":"

      Neovim Treesitter surfaces information from Language Server Protocol (LSP) servers to assist with development and refactor of Clojure code.

      Clojure LSP installation guide shows how to install the Clojure LSP binary for the relevant operating system.

      Once installed, run clojure-lsp -v in a terminal to ensure the command is working.

      practicalli/clojure-lsp-config

      practicalli/clojure-lsp-config provides a complete configuration for clojure-lsp (config.edn), including a wide range of snippets and less restrictive formatting rules (cljfmt.edn)

      clj-kondo provides static analysis of source code files, providing subtle warnings as Clojure code is written to help the developer follow idioms and avoid syntatic errors.

      Clojure LSP includes clj-kondo to provide an implementation of the Language Server Protocol for the Clojure Language.

      Clojure LSP installation guide Treesitter Fennel Configuration

      "},{"location":"install/clojure/#leiningen","title":"Leiningen","text":"

      Many existing Clojure projects use Leiningen build automation tool (although many new projects use Clojure CLI as well or instead of Leiningen).

      The code is the same regardless of tooling choice. The overall workflow is the same, although Clojure CLI may provide more workflow options.

      Follow the install instructions at Leiningen.org if required.

      "},{"location":"install/neovim/","title":"Install Neovim","text":"

      Neovim releases

      Neovim 8 is the minimum version for this configuration and Neovim 0.9.0 is currently being tested.

      Follow the install Neovim guide for the specific operating system.

      "},{"location":"install/neovim/#suppoting-tools","title":"Suppoting Tools","text":"

      Neovim uses several external tools for searching for files, search file contents and using the operating system clipbaord.

      AstroNvim requires node.js

      AstroNvim uses Mason to install LSP servers, format and lint tools. Many LSP servers require node.js to install and function.

      Node.js install - Practicalli Engineering Playbook

      Debian / Ubuntu

      Install the following packages to support Neovim

      • ripgrep fast file contents search (used by telescope)
      • find-fd advanced search tool
      • xclip clipboard as a provider tools for Neovim copy/paste
      • luarocks for LSP servers (AstroNvim)
      sudo apt install find-fd xclip luarocks\n

      Add set clipboard+=unnamedplus to the Neovim configuration to use the Linux clipboard tool

      Wayland requires wl-clipboard

      Install the wl-clipboard package to use the Wayland desktop clipboard with Neovim

      sudo apt install wl-clipboard\n

      "},{"location":"install/neovim/#install-neovim_1","title":"Install Neovim","text":"Linux AppImageUbuntu/DebianBuild from Source

      Download the AppImage from the Neovim Release page and place the file on the executable path, e.g. $HOME/.local/bin

      Make the AppImage executable

      chmod u+x nvim.appimage\n

      Run neovim from the AppImage

      nvim.appimage\n

      Create a symbolic link called nvim to the nvim.appimage

      ln -s $HOME/.local/bin/nvim.appimage $HOME/.local/bin/nvim\n

      Download the Linux AppImage from the Neovim Releases page

      Or build Neovim from source and generate a .deb file from the build.

      Linux version only packaged as AppImage from Neovim 0.9 onward

      Neovim Build Prerequisites for each operating system

      Ubuntu/Debian Packages

      Install packages to support building Neovim

      sudo apt-get install ninja-build gettext cmake unzip curl\n

      Clone the Neovim GitHub repository

      git clone --origin neovim https://github.com/neovim/neovim.git\n
      Change into the cloned directory and change to the stable release to build version 0.9.0

      git checkout stable\n

      Build a release

      make CMAKE_BUILD_TYPE=Release                                                                                                              \u2500\u256f\n

      Once the nvim release has been built, create a debian package for use with Ubuntu and Debian systems

      cpack -G DEB\n
      "},{"location":"install/neovim/#post-install-checks","title":"Post Install checks","text":"

      Ensure supporting tools and binaries are available in the operating system by running the Neovim Heath Check.

      nvim in a terminal to run NeoVim and check the installation is working without error.

      :checkhealth to run a check supporting tools are available to NeoVim.

      A report is generated and shown in NeoVim

      j / k to scroll through the checkhealth report

      Review the warnings and install tooling that is required for languages that will be used.

      Ignore Provider Warnings

      It is safe to ignore language provider warnings.

      Language Providers can be disabled in the Neovim configuration to remove the warnings from :checkhealth report. Examples of disabling language provders are in the practicalli/neovim-config-redux configuration, covered in the Neovim Config install step

      "},{"location":"introduction/community-projects/","title":"Community Configuration Projects","text":"

      Practicalli Neovim book covers the following configurations:

      "},{"location":"introduction/community-projects/#practicalli-neovim-config-redux","title":"Practicalli Neovim Config Redux","text":"

      Practicalli Neovim Config Redux

      • Fennel configuration
      • Packer package manager & Treesitter support
      • Mnemonic key bindings
      • Telescope selectors
      • Autocompletion (cmp) & snippets (luasnip)
      • Esc with center row keys, e.g. \"fd\"
      "},{"location":"introduction/community-projects/#astronvim-and-practicalli-astronvim-config","title":"AstroNvim and Practicalli AstroNvim Config","text":"

      AstroNvim and Practicalli AstroNvim Config organised configuration with a polished UI

      • Neovim 9 support
      • Lazy for plugins (packages for Neovim)
      • Mason to manage install for LSP, DAP, lint and format tools
      • Treesitter and language parser support
      • Telescope selectors
      • Notification dialogs
      • Autocompletion (cmp) & snippets (luasnip)
      • Neovim 9 background switch (live toggle light & dark theme)
      • Hidden command line cmdheight=0 (Neovim 0.8 onward)
      • Esc with center row keys, e.g. \"fd\" (user: plugins/core.lua)
      "},{"location":"introduction/community-projects/#alternative-configurations","title":"Alternative configurations","text":"

      Practicalli Neovim does not cover the following Community configurations.

      • Magit Kit fennel configuration from the author of Conjure
      • cajus-nvim inspiration for practicalli/neovim-config-redux
      • LazyVim lazy & mason configuration
      • NvChad polished UI with Lazy optomisations

      Long term project: Fennel config with AstroNvim-like UI experience

      A very long term goal for Practicalli is to create a Neovim configuration written predominatly in Fennel, providing a rich user experience on par with the very polished experience of AstroNvim.

      Lazy and Mason should be used to manage packages and tools (LSP & DAP servers, lint & format tools).

      Which-key should provide a mnemonic menu system similar to the Spacemacs experience.

      "},{"location":"introduction/contributing/","title":"Contributing to Practicalli","text":"

      Practicalli books are written in markdown and use MkDocs to generate the published website via a GitHub workflow. MkDocs can also run a local server using the make docs target from the Makefile

      By submitting content ideas and corrections you are agreeing they can be used in this book under the Creative Commons Attribution ShareAlike 4.0 International license. Attribution will be detailed via GitHub contributors.

      All content and interaction with any persons or systems must be done so with respect and within the Practicalli Code of Conduct.

      "},{"location":"introduction/contributing/#book-status","title":"Book status","text":""},{"location":"introduction/contributing/#submit-and-issue-or-idea","title":"Submit and issue or idea","text":"

      If something doesnt seem quite right or something is missing from the book, please raise an issue via the GitHub repository explaining in as much detail as you can.

      Raising an issue before creating a pull request will save you and the maintainer time.

      "},{"location":"introduction/contributing/#considering-a-pull-request","title":"Considering a Pull request?","text":"

      Before investing any time in a pull request, please raise an issue explaining the situation. This can save you and the maintainer time and avoid rejected pull requests.

      Please keep pull requests small and focused, as they are much quicker to review and easier to accept. Ideally PR's should be for a specific page or at most a section.

      A PR with a list of changes across different sections will not be merged, it will be reviewed eventually though.

      "},{"location":"introduction/contributing/#thank-you-to-everyone-that-has-contributed","title":"Thank you to everyone that has contributed","text":"

      A huge thank you to Rich Hickey and the team at Cognitect for creating and continually guiding the Clojure language. Special thank you to Alex Miller who has provided excellent advice on working with Clojure and the CLI tooling.

      The Clojure community has been highly supportive of everyone using Clojure and I'd like to thank everyone for the feedback and contributions. I would also like to thank everyone that has joined in with the London Clojurins community, ClojureBridgeLondon, Clojurians Slack community, Clojurians Zulip community and Clojureverse community.

      Thank you to everyone who sponsors the Practicalli websites and videos and for the Clojurists Together sponsorship, it helps me continue the work at a much faster pace.

      Special thanks to Bruce Durling for getting me into Cloure in the first place.

      "},{"location":"introduction/features/","title":"Neovim features","text":"

      A clean UI provides for a distraction free development experience, with only the essential information presented in the Neovim statusline or inline with the code

      • Conjure - automatic Clojure REPL connection, evaluation, test runners
      • Treesitter
      • Plug-in Manager (e.g. Lazy.nvim)
      • LSP - auto-completion, snippets, inline linting, reference navigation, refactor and unit test coverage
      • statusline - LSP status, diff changes, filetype, cursor position
      • Selection narrowing completion of files, packages, colour schemes, etc
      • File browser - telescope selection narrowing and visual file system navigation
      • Version Control gutter indicators for changed lines
      • todo comments todo, fix, notes, indicators with gutter icons
      • relative line numbers for vim-style navigation
      "},{"location":"introduction/features/#conjure","title":"Conjure","text":"

      Conjure An interactive environment for evaluating code, e.g. a Clojure REPL. Conjure automatically connects to an nREPL process running in the current project.

      Evaluate Clojure code as its developed for an instant feedback workflow.

      Run unit tests with Kaocha test runner (Cognitect Labs and ClojureScript runners also available)

      Fireplace has been a long-standing plugin for Vim to support Clojure REPL connection.

      "},{"location":"introduction/features/#lazy-plugin-manager","title":"Lazy Plugin manager","text":"

      Lazy.nvim manages neovim plugins with a rich UI that provides an enjoyable user experience. Plugins are automatically installed during startup and lists the status of each plugins.

      Plugins are automatic cached & bytecode compiled and can be lazy loaded to streamline startup time and resource usage based on events, commands, filetypes, and key mappings. Efficient plugin downlaods using partial blobless clones of plugin repositories, i.e. --filter=blob:none

      Lazy.nvim

      "},{"location":"introduction/features/#treesitter","title":"Treesitter","text":"

      Neovim provides highly effective syntax highlighting of source code due to Treesitter.

      Tree-sitter parses files opened in Neovim and builds a concrete syntax tree that any Neovim plugin can use to efficiently provide feedback. Treesitter uses incremental parsing to efficiently update the syntax tree as a file is edited.

      • parse on every keystroke in a text editor
      • provide useful results even in the presence of syntax errors

      Treesitter

      "},{"location":"introduction/features/#language-server-protocol","title":"Language Server Protocol","text":"

      Neovim includes an LSP client which uses the information recieved from a language specific LSP server in real-time to provide a range of services:

      • auto-completion of function and symbol names
      • live linting as code is typed or opened from a file
      • formatting
      • function signatures and help documentation
      • diagnostics (syntax errors & idioms)
      • symantic analysis providing rename through project, go-to-definition & find-references

      LSP feedback is often presented in the buffer, file browser and status line of Neovim.

      LSP Server implementation is not universal

      LSP is a relatively new specification and many server implmentations are still evolving or are yet to be created.

      Lint tools tend to be more prevelent and may be required in concert with or in the absence of an LSP server.

      LSP related Plugins
      • neovim/nvim-lspconfig - connect Neovim lsp client to lsp servers
      • jose-elias-alvarez/null-ls.nvim - hook format and lint tools into the Neovim LSP client
      • jayp0521/mason-null-ls.nvim - automatically install formatters/linters to be used by null-ls
      • williamboman/mason - install and manage LSP servers, DAP servers, linters, and formatters
      • williamboman/mason-lspconfig - register LSP configs with neovim so LSP client can connect to servers
      "},{"location":"introduction/features/#lint-and-format-tools","title":"Lint and format tools:","text":"

      Linters check code for common problems and provide hints on how to correct any detected issues.

      Format tools suppor code to conforming to a specified coding style, typically these run when save-file is run.

      null-ls provides extensive builtin configuration for programming languages and configuration formats. null-ls also passes lint and format tool information to the Neovim LSP client, extending the range of language support.

      "},{"location":"introduction/features/#selection-narrowing","title":"Selection Narrowing","text":"

      telescope.nvim is a highly extendable fuzzy finder over lists with community driven pickers, sorters and previewers.

      Navigate and narrow lists of files, packages, environment variables, ports, colour schemes (themes) and any other list of items effectively.

      Telescope File browser popup also explores the file system and in Normal mode can be used to create files and directories

      The telescope list narrows matches as characters are typed

      "},{"location":"introduction/features/#version-control","title":"Version Control","text":"

      Gitsigns hightlights buffer changes in the gutter

      Lualine shows number of Git changes in status line

      Diffview to review all changes for any git revision

      Neogit provides a rich git client to add, stash, commit, push & pull changes.

      Octo provides a GitHub specific client to manage issues and pull requests, using GitHub CLI authentication.

      LazyGit UI

      "},{"location":"introduction/features/#file-browser","title":"File Browser","text":"

      neo-tree provides a visual file system explorer that can also create and delete files and directories

      "},{"location":"introduction/features/#todo-comments","title":"TODO Comments","text":"

      Highlight tasks, fixes, notes and dragons comments, including icons in the gutter. Use Telescope to navigate TODO comments in the current project.

      "},{"location":"introduction/features/#status-line","title":"Status Line","text":"

      LSP feedback

      "},{"location":"introduction/features/#markdown","title":"Markdown","text":"
      • LSP server
      • Marksman: select anchors and pages for links
      "},{"location":"introduction/fennel/","title":"Fennel","text":"

      Lua is the defacto language for Neovim plugin development and configuration.

      Fennel can be used to write Neovim packages and configuration, using nfnl to generate the equivalent Lua code that Neovim runs.

      Although Neovim fully supports Vimscript, Practicalli encourages Fennel or Lua, as Vimscript is a niche language with quite complex syntax.

      "},{"location":"introduction/fennel/#overview","title":"Overview","text":"

      Fennel is a programming language that brings together the speed, simplicity, and reach of Lua with the flexibility of a lisp syntax and macro system.

      • Full Lua compatibility: Easily call any Lua function or library from Fennel and vice-versa.
      • Zero overhead: Compiled code should be just as efficient as hand-written Lua.
      • Compile-time macros: Ship compiled code with no runtime dependency on Fennel.
      • Embeddable: Fennel is a one-file library as well as an executable. Embed it in other programs to support runtime extensibility and interactive development.

      Anywhere you can run Lua code, you can run Fennel code.

      Translate Lua to Fennel

      See Fennel is an online antifennel tool to convert Lua to Fennel or Fennel to Lua.

      practicalli/neovim-config-redux configuration provides helper functions to minimise the translation required.

      "},{"location":"introduction/fennel/#fennel-packages","title":"Fennel Packages","text":"

      The Conjure package which provides the Clojure REPL (and much more) is written in Fennel.

      "},{"location":"introduction/fennel/#nfnl","title":"nfnl","text":"

      nfnl generates Lua code from Fennel code. Neovim runs the generated Lua code.

      nfnl loads only when working in directories containing a .nfnl.fnl configuration file, so has zero overhead when not working with fennel.

      *.fnl files are automatically compiled to *.lua when changes are saved, showing any compilation errors to provide an effective feedback loop.

      nfnl standard library

      nfnl plugin example

      "},{"location":"introduction/fennel/#development-tooling","title":"Development tooling","text":"

      Neovim support

      • Anti-fennel - convert from Lua code to Fennel code.
      • nfnl - write plugins or configuration for Neovim with great runtime performance
      • hotpot - seamless Fennel inside Neovim

      See Fennel is an online antifennel tool to convert between Lua and Fennel.

      Guide to plugin development with fennel

      Emacs support:

      • technomancy/fennel-mode and Emacs mirror repository
      "},{"location":"introduction/fennel/#playing-games","title":"Playing Games","text":"

      TIC-80 is a simulated computer environment to to write code, design art, compose music and retro style game games.

      L\u00d6VE is a framework for making games with the Lua programming language, allows import from external resources and can use any resolution or memory resources required.

      TIC-80 and L\u00d6VE provide cross-platform support across Windows, Mac and Linux systems. TIC-80 games can also be played in the browser.

      "},{"location":"introduction/repl-workflow/","title":"REPL Driven Development","text":"

      Always be REPL'ing

      Coding without a REPL feels limiting. The REPL provides fast feedback from code as its crafted, testing assumptions and design choices every step of the journey to a solution - John Stevenson, Practical.li

      Clojure is a powerful, fun and highly productive language for developing applications and services. The clear language design is supported by a powerful development environment known as the REPL (read, evaluate, print, loop). The REPL gives you instant feedback on what your code does and enables you to test either a single expression or run the whole application (including tests).

      REPL driven development is the foundation of working with Clojure effectively

      An effective Clojure workflow begins by running a REPL process. Clojure expressions are written and evaluated immediately to provide instant feedback. The REPL feedback helps test the assumptions that are driving the design choices.

      • Read - code is read by the Clojure reader, passing any macros to the macro reader which converts those macros into Clojure code.
      • Evaluate - code is compiled into the host language (e.g. Java bytecode) and executed
      • Print - results of the code are displayed, either in the REPL or as part of the application.
      • Loop - the REPL is a continuous process that evaluates code, either a single expression or the whole application.

      Design decisions and valuable data from REPL experiments can be codified as specifications and unit tests

      Practicalli REPL Reloaded Workflow

      The principles of REPL driven development are implemented in practice using the Practicalli REPL Reloaded Workflow and supporting tooling. This workflow uses Portal to inspect all evaluation results and log events, hot-load libraries into the running REPL process and reloads namespaces to support major refactor changes.

      "},{"location":"introduction/repl-workflow/#evaluating-source-code","title":"Evaluating source code","text":"

      A REPL connected editor is the primary tool for evaluating Clojure code from source code files, displaying the results inline.

      Source code is automatically evaluated in its respective namespace, removing the need to change namespaces in the REPL with (in-ns) or use fully qualified names to call functions.

      Evaluate Clojure in Neovim with Conjure

      , e b evaluates the code in the current buffer

      Evaluate Clojure in a Terminal UI REPL

      Entering expressions at the REPL prompt evaluates the expression immediately, returning the result directly underneath

      "},{"location":"introduction/repl-workflow/#rich-comment-blocks-living-documentation","title":"Rich Comment blocks - living documentation","text":"

      The (comment ,,,) function wraps code that is only run directly by the developer using a Clojure aware editor.

      Expressions in rich comment blocks can represent how to use the functions that make up the namespace API. For example, starting/restarting the system, updating the database, etc. Expressions provide examples of calling functions with typical arguments and make a project more accessible and easier to work with.

      Clojure Rich Comment to manage a service

      (ns practicalli.gameboard.service)\n\n(defn app-server-start [port] ,,,)\n(defn app-server-start [] ,,,)\n(defn app-server-restart [] ,,,)\n\n(defn -main\n  \"Start the service using system components\"\n  [& options] ,,,)\n\n(comment\n  (-main)\n  (app-server-start 8888)\n  (app-server-stop)\n  (app-server-restart 8888)\n\n  (System/getenv \"PORT\")\n  (def environment (System/getenv))\n  (def system-properties (System/getProperties))\n  ) ; End of rich comment block\n

      Rich comment blocks are very useful for rapidly iterating over different design decisions by including the same function but with different implementations. Hide clj-kondo linter warnings for redefined vars (def, defn) when using this approach.

      ;; Rich comment block with redefined vars ignored\n#_{:clj-kondo/ignore [:redefined-var]}\n(comment\n  (defn value-added-tax []\n    ;; algorithm design - first idea)\n\n  (defn value-added-tax []\n    ;; algorithm design - second idea)\n\n  ) ;; End of rich comment block\n

      The \"Rich\" in the name is an honourary mention to Rich Hickey, the author and benevolent dictator of Clojure design.

      "},{"location":"introduction/repl-workflow/#design-journal","title":"Design Journal","text":"

      A journal of design decisions makes the code easier to understand and maintain. Code examples of design decisions and alternative design discussions are captured, reducing the time spent revisiting those discussions.

      Journals simplify the developer on-boarding processes as the journey through design decisions are already documented.

      A Design Journal is usually created in a separate namespace, although it may start as a rich comment at the bottom of a namespace.

      A journal should cover the following aspects

      • Relevant expressions use to test assumptions about design options.
      • Examples of design choices not taken and discussions why (saves repeating the same design discussions)
      • Expressions that can be evaluated to explain how a function or parts of a function work

      The design journal can be used to create meaningful documentation for the project very easily and should prevent time spent on repeating the same conversations.

      Example design journal

      Design journal for TicTacToe game using Reagent, ClojureScript and Scalable Vector Graphics

      "},{"location":"introduction/repl-workflow/#viewing-data-structures","title":"Viewing data structures","text":"

      Pretty print shows the structure of results from function calls in a human-friendly form, making it easier for a developer to parse and more likely to notice incorrect results.

      Tools to view and navigate code

      • Cider inspector is an effective way to navigate nested data and page through large data sets.
      • Portal Inspector to visualise many kinds of data in many different forms.

      "},{"location":"introduction/repl-workflow/#code-style-and-idiomatic-clojure","title":"Code Style and idiomatic Clojure","text":"

      Clojure aware editors should automatically apply formatting that follows the Clojure Style guide.

      Live linting with clj-kondo suggests common idioms and highlights a wide range of syntax errors as code is written, minimizing bugs and therefore speeding up the development process.

      Clojure LSP is build on top of clj-kondo

      Clojure LSP uses clj-kondo static analysis to provide a standard set of development tools (format, refactor, auto-complete, syntax highlighting, syntax & idiom warnings, code navigation, etc).

      Clojure LSP can be used with any Clojure aware editor that provides an LSP client, e.g. Spacemacs, Doom Emacs, Neovim, VSCode.

      Clojure Style Guide

      The Clojure Style guide provides examples of common formatting approaches, although the development team should decide which of these to adopt. Emacs clojure-mode will automatically format code and so will Clojure LSP (via cljfmt). These tools are configurable and should be tailored to the teams standard.

      "},{"location":"introduction/repl-workflow/#data-and-function-specifications","title":"Data and Function specifications","text":"

      Clojure spec is used to define a contract on incoming and outgoing data, to ensure it is of the correct form.

      As data structures are identified in REPL experiments, create data specification to validate the keys and value types of that data.

      ;; ---------------------------------------------------\n;; Address specifications\n(spec/def ::house-number string?)\n(spec/def ::street string?)\n(spec/def ::postal-code string?)\n(spec/def ::city string?)\n(spec/def ::country string?)\n(spec/def ::additional string?)\n\n(spec/def ::address   ; Composite data specification\n  (spec/keys\n   :req-un [::street ::postal-code ::city ::country]\n   :opt-un [::house-number ::additional]))\n;; ---------------------------------------------------\n

      As the public API is designed, specifications for each functions arguments are added to validate the correct data is used when calling those functions.

      Generative testing provides a far greater scope of test values used incorporated into unit tests. Data uses clojure.spec to randomly generate data for testing on each test run.

      "},{"location":"introduction/repl-workflow/#test-driven-development-and-repl-driven-development","title":"Test Driven Development and REPL Driven Development","text":"

      Test Driven Development (TDD) and REPL Driven Development (RDD) complement each other as they both encourage incremental changes and continuous feedback.

      Test Driven Development fits well with Hammock Time, as good design comes from deep thought

      • RDD enables rapid design experiments so different approaches can easily and quickly be evaluated .
      • TDD focuses the results of the REPL experiments into design decisions, codified as unit tests. These tests guide the correctness of specific implementations and provide critical feedback when changes break that design.

      Unit tests should support the public API of each namespace in a project to help prevent regressions in the code. Its far more efficient in terms of thinking time to define unit tests as the design starts to stabilize than as an after thought.

      clojure.test library is part of the Clojure standard library that provides a simple way to start writing unit tests.

      Clojure spec can also be used for generative testing, providing far greater scope in values used when running unit tests. Specifications can be defined for values and functions.

      Clojure has a number of test runners available. Kaocha is a test runner that will run unit tests and function specification checks.

      Automate local test runner

      Use kaocha test runner in watch mode to run tests and specification check automatically (when changes are saved)

      clojure -X:test/watch\n

      "},{"location":"introduction/repl-workflow/#continuous-integration-and-deployment","title":"Continuous Integration and Deployment","text":"

      Add a continuous integration service to run tests and builds code on every shared commit. Spin up testable review deployments when commits pushed to a pull request branch, before pushing commits to the main deployment branch, creating an effective pipeline to gain further feedback.

      • CircleCI provides a simple to use service that supports Clojure projects.
      • GitHub Workflows and GitHub actions marketplace to quickly build a tailored continuous integration service, e.g. Setup Clojure GitHub Action.
      • GitLab CI

      "},{"location":"introduction/repl-workflow/#live-coding-with-data-stuart-halloway","title":"Live Coding with Data - Stuart Halloway","text":"

      There are few novel features of programming languages, but each combination has different properties. The combination of dynamic, hosted, functional and extended Lisp in Clojure gives developers the tools for making effective programs. The ways in which Clojure's unique combination of features can yield a highly effective development process.

      Over more than a decade we have developed an effective approach to writing code in Clojure whose power comes from composing many of its key features. As different as Clojure programs are from e.g. Java programs, so to can and should be the development experience. You are not in Kansas anymore!

      This talk presents a demonstration of the leverage you can get when writing programs in Clojure, with examples, based on my experiences as a core developer of Clojure and Datomic.

      "},{"location":"introduction/writing-tips/","title":"Writing tips for MkDocs","text":"

      Making the docs more engaging using the mkdocs-material theme reference guide

      Configuring Colors

      Material for MkDocs - Changing the colors lists the primary and accent colors available.

      HSL Color Picker for codes to modify the theme style, overriding colors in docs/assets/stylesheets/extra.css

      "},{"location":"introduction/writing-tips/#hypertext-links","title":"Hypertext links","text":"

      Links open in the same browser window/tab by default.

      Add {target=_blank} to the end of a link to configure opening in a new tab

      [link text](url){target=_blank}\n
      "},{"location":"introduction/writing-tips/#buttons","title":"Buttons","text":"

      Convert any link into a button by adding {.md-button} class names to end of the markdown for a link, which uses .md-button-primary by default. Include target=_blank for buttons with links to external sites.

      [link text](http://practical.li/blog){.md-button target=_blank}\n

      Or specify a different class

      [link text](http://practical.li/blog){.md-button .md-button-primary}\n

      Add an icon to the button

      Practicalli Issues Practicalli Blog

      [:fontawesome-brands-github: Practicalli Issues](http://practical.li/blog){ .md-button .md-button-primary }\n[:octicons-heart-fill-24: Practicalli Blog](http://practical.li/blog){ .md-button .md-button-primary }\n

      Search all supported icons

      "},{"location":"introduction/writing-tips/#youtube-video","title":"YouTube video","text":"

      Use an iframe element to include a YouTube video, wrapping in a paragraph tag with center alignment to place the video in a centered horizontal position

      <p style=\"text-align:center\">\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/rQ802kSaip4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n</p>\n

      mkdocs material does not have direct support for adding a YouTube video via markdown.

      "},{"location":"introduction/writing-tips/#admonitions","title":"Admonitions","text":"

      Supported admonition types

      Note

      Use !!! followed by NOTE

      Adding a title

      Use !!! followed by NOTE and a \"title in double quotes\"

      Shh, no title bar just the text... Use !!! followed by NOTE and a \"\" empty double quotes

      Abstract

      Use !!! followed by ABSTRACT

      Info

      Use !!! followed by INFO

      Tip

      Use !!! followed by TIP

      Success

      Use !!! followed by SUCCESS

      Question

      Use !!! followed by QUESTION

      Warning

      Use !!! followed by WARNING

      Failure

      Use !!! followed by FAILURE

      Danger

      Use !!! followed by DANGER

      Bug

      Use !!! followed by BUG

      Example

      Use !!! followed by EXAMPLE

      Quote

      Use !!! followed by QUOTE

      "},{"location":"introduction/writing-tips/#collapsing-admonitions","title":"Collapsing admonitions","text":"Note

      Collapse those admonitions using ??? instead of !!!

      Replace with a title

      Use ??? followed by NOTE and a \"title in double quotes\"

      Expanded by default

      Use ???+, note the + character, followed by NOTE and a \"title in double quotes\"

      "},{"location":"introduction/writing-tips/#inline-blocks","title":"Inline blocks","text":"

      Inline blocks of text to make a very specific callout within text

      Info

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Adding something to then end of text is probably my favourite

      Info

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      "},{"location":"introduction/writing-tips/#code-blocks","title":"Code blocks","text":"

      Code blocks include a copy icon automatically

      Syntax highlighting in code blocks

      (defn my-function  ; Write a simple function\n  \"With a lovely doc-string\"\n  [arguments]\n  (map inc [1 2 3]))\n

      Give the code block a title using title=\"\" after the backtics and language name

      src/practicalli/gameboard.clj
      (defn my-function\n  \"With a lovely doc-string\"\n  [arguments]\n  (map inc [1 2 3]))\n

      We all like line numbers, especially when you can set the starting line

      src/practicalli/gameboard.clj
      (defn my-function\n  \"With a lovely doc-string\"\n  [arguments]\n  (map inc [1 2 3]))\n

      Add linenums=42 to start line numbers from 42 onward

      clojure linenums=\"42\" title=\"src/practicalli/gameboard.clj\"\n
      "},{"location":"introduction/writing-tips/#annotations","title":"Annotations","text":"

      Annotations in a code block help to highlight important aspects. Use the comment character for the language followed by a space and a number in brackets

      For example, in a shell code block, use # (1) where 1 is the number of the annotation

      Use a number after the code block to add the text for the annotation, e.g. 1.. Ensure there is a space between the code block and the annotation text.

      ls -la $HOME/Downloads  # (1)\n
      1. I'm a code annotation! I can contain code, formatted text, images, ... basically anything that can be written in Markdown.

      Code blocks with annotation, add ! after the annotation number to suppress the # character

      (defn helper-function\n  \"Doc-string with description of function purpose\" ; (1)!\n  [data]\n  (merge {:fish 1} data)\n  )\n
      1. Always include a doc-string in every function to describe the purpose of that function, identifying why it was added and what its value is.

      GitHub action example with multiple annotations

      name: ci # (1)!\non:\n  push:\n    branches:\n      - master # (2)!\n      - main\npermissions:\n  contents: write\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v4\n        with:\n          python-version: 3.x\n      - run: pip install mkdocs-material # (3)!\n      - run: mkdocs gh-deploy --force\n
      1. You can change the name to your liking.

      2. At some point, GitHub renamed master to main. If your default branch is named master, you can safely remove main, vice versa.

      3. This is the place to install further [MkDocs plugins] or Markdown extensions with pip to be used during the build:

        pip install \\\n  mkdocs-material \\\n  mkdocs-awesome-pages-plugin \\\n  ...\n
      "},{"location":"introduction/writing-tips/#highlight-lines-in-code-blocks","title":"Highlight lines in code blocks","text":"

      Add highlight line meta data to a code block after the opening backticks and code block language.

      hl_lines=\"2\" highlights line 2 in the codeblock

      hl_lines=\"2 3 4\" highlights line 2, 3 and 4 in the codeblock

      (defn my-function\n  \"With a lovely doc-string\"\n  [arguments]\n  (map\n   inc\n   [1 2 3]))\n
      "},{"location":"introduction/writing-tips/#embed-external-files","title":"Embed external files","text":"

      --8<-- in a code block inserts code from a source code file or other text file

      Specify a local file from the root of the book project (the directory containing mkdocs.yml)

      Scheduled Version Check GitHub Workflow from source code file scheduled version check
      ---\n# ------------------------------------------\n# Scheduled check of versions\n# - use as non-urgent report on versions\n# - Uses POSIX Cron syntax\n#   - Minute [0,59]\n#   - Hour [0,23]\n#   - Day of the month [1,31]\n#   - Month of the year [1,12]\n#   - Day of the week ([0,6] with 0=Sunday)\n#\n# Using liquidz/anta to check:\n# - GitHub workflows\n# - deps.edn\n# ------------------------------------------\n\nname: \"Scheduled Version Check\"\non:\n  schedule:\n    # - cron: \"0 4 * * *\" # at 04:04:04 ever day\n    # - cron: \"0 4 * * 5\" # at 04:04:04 ever Friday\n    - cron: \"0 4 1 * *\" # at 04:04:04 on first day of month\n  workflow_dispatch: # Run manually via GitHub Actions Workflow page\n\njobs:\n  scheduled-version-check:\n    name: \"Scheduled Version Check\"\n    runs-on: ubuntu-latest\n    steps:\n      - run: echo \"\ud83d\ude80 Job automatically triggered by ${{ github.event_name }}\"\n      - run: echo \"\ud83d\udc27 Job running on ${{ runner.os }} server\"\n      - run: echo \"\ud83d\udc19 Using ${{ github.ref }} branch from ${{ github.repository }} repository\"\n\n      - name: \"Checkout code\"\n        uses: actions/checkout@v4\n      - run: echo \"\ud83d\udc19 ${{ github.repository }} repository was cloned to the runner.\"\n\n      - name: \"Antq Check versions\"\n        uses: liquidz/antq-action@main\n        with:\n          excludes: \"\"\n          skips: \"boot clojure-cli pom shadow-cljs leiningen\"\n\n      # Summary\n      - run: echo \"\ud83c\udfa8 library versions checked with liquidz/antq\"\n      - run: echo \"\ud83c\udf4f Job status is ${{ job.status }}.\"\n
      Practicalli Project Templates Emacs project configuration - .dir-locals.el
      ((clojure-mode . ((cider-preferred-build-tool . clojure-cli)\n                  (cider-clojure-cli-aliases . \":test/env:dev/reloaded\"))))\n

      Code example reuse

      Use an embedded local or external file (URL) when the same content is required in more than one place in the book.

      An effective way of sharing code and configuration mutliple times in a book or across multiple books.

      "},{"location":"introduction/writing-tips/#content-tabs","title":"Content tabs","text":"

      Create in page tabs that can also be

      Setting up a project

      Clojure CLILeiningen
      clojure -T:project/new :template app :name practicalli/gameboard\n
      lein new app practicalli/gameboard\n

      Or nest the content tabs in an admonition

      Run a terminal REPL

      Clojure CLILeiningen
      clojure -T:repl/rebel\n
      lein repl\n
      "},{"location":"introduction/writing-tips/#diagrams","title":"Diagrams","text":"

      Neat flow diagrams

      Diagrams - Material for MkDocs

      graph LR\n  A[Start] --> B{Error?};\n  B -->|Yes| C[Hmm...];\n  C --> D[Debug];\n  D --> B;\n  B ---->|No| E[Yay!];

      UML Sequence Diagrams

      sequenceDiagram\n  Alice->>John: Hello John, how are you?\n  loop Healthcheck\n      John->>John: Fight against hypochondria\n  end\n  Note right of John: Rational thoughts!\n  John-->>Alice: Great!\n  John->>Bob: How about you?\n  Bob-->>John: Jolly good!

      state transition diagrams

      stateDiagram-v2\n  state fork_state <<fork>>\n    [*] --> fork_state\n    fork_state --> State2\n    fork_state --> State3\n\n    state join_state <<join>>\n    State2 --> join_state\n    State3 --> join_state\n    join_state --> State4\n    State4 --> [*]

      Class diagrams - not needed for Clojure

      Entity relationship diagrams are handy though

      erDiagram\n  CUSTOMER ||--o{ ORDER : places\n  ORDER ||--|{ LINE-ITEM : contains\n  LINE-ITEM {\n    customer-name string\n    unit-price int\n  }\n  CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
      "},{"location":"introduction/writing-tips/#keyboard-keys","title":"Keyboard keys","text":"

      Represent key bindings with Keyboard keys. Each number and alphabet character has their own key.

      • 1 ++1++ for numbers
      • l ++\"l\"++ for lowercase character
      • U ++u++ for uppercase character or ++\"U\"++ for consistency

      Punctionation keys use their name

      • Space ++spc++
      • , ++comma++
      • Left ++arrow-left++

      For key sequences, place a space between each keyboard character

      • Space g s ++spc++ ++\"g\"++ ++\"s\"++

      For key combinations, use join they key identifies with a +

      • Meta+X ++meta+x++
      • Ctrl+Alt+Del ++ctrl+alt+del++

      MkDocs keyboard keys reference

      "},{"location":"introduction/writing-tips/#images","title":"Images","text":"

      Markdown images can be appended with material tags to set the size of the image, whether to appear on light or dark theme and support lazy image loading in browsers

      SizeLazy LoadingAlignTheme SpecificAll Image Attributes

      {style=\"height:150px;width:150px\"} specifies the image size

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png#only-dark){style=\"height:150px;width:150px\"}\n

      {loading=lazy} specifies an image should lazily load in the browser

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png){loading=lazy}\n

      {aligh=left} or {aligh=right} specifies the page alignment of an image.

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png#only-dark){align=right}\n![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-dark.png#only-light){align=right}\n

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      ![Kitty Logo](image/kitty-light.png#only-dark) or ![Kitty Logo](image/kitty-light.png#only-light) specifies the theme the image should be shown, allowing different versions of images to be shown based on the theme.

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png#only-dark){style=\"height:150px;width:150px\"}\n![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-dark.png#only-light){style=\"height:150px;width:150px\"}\n
      Use the theme toggle in the top nav bar to see the icon change between light and dark.

      Requires the color pallet toggle

      Alight right, lazy load and set image to 150x150

      ![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-light.png#only-dark){align=right loading=lazy style=\"height:64px;width:64px\"}\n![Kitty Logo](https://raw.githubusercontent.com/practicalli/graphic-design/live/icons/kitty-dark.png#only-light){align=right loading=lazy style=\"height:64px;width:64px\"}\n

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

      "},{"location":"introduction/writing-tips/#lists","title":"Lists","text":"

      Task lists

      • Lorem ipsum dolor sit amet, consectetur adipiscing elit
      • Vestibulum convallis sit amet nisi a tincidunt
      • In hac habitasse platea dictumst
      • In scelerisque nibh non dolor mollis congue sed et metus
      • Praesent sed risus massa
      • Aenean pretium efficitur erat, donec pharetra, ligula non scelerisque

      Task List example

      - [x] Lorem ipsum dolor sit amet, consectetur adipiscing elit\n- [ ] Vestibulum convallis sit amet nisi a tincidunt\n    * [x] In hac habitasse platea dictumst\n    * [x] In scelerisque nibh non dolor mollis congue sed et metus\n    * [ ] Praesent sed risus massa\n- [ ] Aenean pretium efficitur erat, donec pharetra, ligula non scelerisque\n
      "},{"location":"introduction/writing-tips/#tooltips","title":"Tooltips","text":"

      The humble tool tip

      Hover me

      with references

      Hover me

      Icon tool tip with a title

      "},{"location":"introduction/writing-tips/#abreviations","title":"Abreviations","text":"

      The HTML specification is maintained by the W3C.

      [HTML]: Hyper Text Markup Language [W3C]: World Wide Web Consortium

      "},{"location":"introduction/writing-tips/#magic-links","title":"Magic links","text":"

      MagicLink can auto-link HTML, FTP, and email links. It can auto-convert repository links (GitHub, GitLab, and Bitbucket) and display them in a more concise, shorthand format.

      Email Practicalli

      Practicalli Neovim

      "},{"location":"neovim-basics/","title":"Using Neovim","text":"

      The fundamental controls of Neovim which apply across all editing tasks.

      "},{"location":"neovim-basics/#fundamentals","title":"Fundamentals","text":"

      File Buffer Window and Tab page Multi-modal Editing

      "},{"location":"neovim-basics/#editing-tools","title":"Editing Tools","text":"

      Multiple Cursors

      "},{"location":"neovim-basics/#writing-tools","title":"Writing Tools","text":"

      Snippets

      "},{"location":"neovim-basics/#development-tools","title":"Development Tools","text":"

      Comments Clojure Development Version Control

      Format and Lint tools are installed via Mason

      "},{"location":"neovim-basics/#spellcheck","title":"Spellcheck","text":"AstroNvim

      SPC u s toggles spellcheck, marking misspelt words with a rew wavy underline

      ] s jumps to next misspelt word, [ s jumps to previous misspelt word,

      z = shows numbered list of possible words, enter the number next to the work to replace the misspelt word.

      z g to add the current word to the spell list, infroming spellcheck that this is a correct word.

      "},{"location":"neovim-basics/comments/","title":"Comments","text":"

      comment.nvim toggles a comment for lines, visual selections or for motions

      gcc comment current line, 4gcc comment current line and next 4 lines

      gc comment region or use with motion e.g. gcap comment paragraph,

      gc in operator pending mode to target a comment TODO: what is operator pending mode

      :7,17Commentary comment a range

      :g/TODO/Commentary as part of a :global invocation

      gcgc removes comments from a set of adjacent commented lines.

      "},{"location":"neovim-basics/file-buffer-window-tab/","title":"Files Buffers Windows and Tabs","text":"

      Files are text written to perminant storage, e.g. disk or usb drive and are names with an extension that represents the file type, e.g. .clj for clojure, .md for markdown, etc.

      A Buffer hold the contents of a file or any other information from processes, e.g. the REPL evaluation log.

      Windows are a view on a buffer and windows can swap which buffer they show. Multiple windows, also known as splits, can be present in a Neovim frame.

      A tab page (or tab) can hold one or more windows and multiple tab pages can be shown on a tab-line.

      "},{"location":"neovim-basics/file-buffer-window-tab/#files","title":"Files","text":"

      Use Neo-tree.nvim to visually navigate and manage files using a tree view of the current project. Files and directories can be added, renamed, moved and deleted.

      Use Telescope to select files, typing a name narrows the file list.

      "},{"location":"neovim-basics/file-buffer-window-tab/#using-neo-tree","title":"Using Neo-tree","text":"AstronvimPracticalli Neovim Config Redux

      Space e toggles neo-tree file browser

      Space o toggles between buffer and neo-tree

      Space f t t to open file explorer

      Within Neo-tree:

      h j k l to navigate the file tree hierachy

      < and > to navigate between File, Bufs and Git sources tabs

      ? shows neotree help, listing key bindings

      a adds a file, prompting for a name relative to the directory where a was pressed. The name can include new directories to be created. A name ending with ++forward-slash++ will create a directory rather than a file.

      d deletes the current file or directory (including sub-directories), a conformation prompt is shown

      r to rename a file or directory (use move to change the path)

      m to move a file or directory, optionally renaming too

      Neotree icons

      • yellow dot - unsaved changes
      • pencil - git added changes
      • cross - git deleted changes
      • Warning triangle - lsp diagnostics issues
      "},{"location":"neovim-basics/file-buffer-window-tab/#telescope","title":"Telescope","text":"

      Telescope provides a selector which will narrow the list of matches as a pattern is typed, providing a fast way to find an item in a list.

      Telescope provides a preview of the selected file (only if there is sufficient space in the Neovim frame)

      File lists are relative to the directory Neovim was opened from (or Path subsequently set in Neovim).

      AstroNvimPracticalli Neovim Config Redux

      SPC f f selector for files within the scope of the current directory path. SPC f F to also show hidden files from the current directory path.

      SPC f a selector for AstroNvim user configuration files

      SPC f p selector for previously opened files (oldfiles)

      SPC f f to list files within the scope of the current directory path.

      SPC f b provides a file browser to open files, navigate the file space and create new files and directories

      "},{"location":"neovim-basics/file-buffer-window-tab/#save-file","title":"Save File","text":"

      Files and directories are created in the path given, relative to the directory in which Neovim was opened.

      A file must exist for Neovim to write to it. Neo-tree and Telescope can be used to create files and directories, as can a terminal and the command line integration (!)

      AstronvimPracticalli Neovim Config ReduxNeovim

      Space w will write all buffer changes to the associate file.

      Space n creates a new buffer that can be written to a given file using :write path/to/filename

      :write path/to/filename will write the current buffer to a new file.

      SPC f b ESC C to create a new file or directory. The base path is shown in the command bar. Type the name of directories and file name as required. RTN to create or ESC to cancel. The newly created directories or file name appears n the Telescope list and scan be selected for opening.

      Telescope Normal mode and help

      ESC in Telescope to switch to Normal mode and use comannds, c for Create, r to rename.

      ? to show all the commands available in Telescope

      :lcd to set the current local directory

      :write path/to/filename will write the current buffer to a new file

      :!mkdir path/to/directory will create a new directory

      If a file is already opened, i.e. with :edit, there is some short-hand syntax to simplify the typing

      :!mkdir -p %:h

      -p option createst any parts of the path required to make the full path

      % is the neovim name of the current file

      :h for the current directory (the \u201chead\u201d of the path).

      ! is the NeoVim terminal shell command, e.g. :!mkdir -p path/to/new/directory creates a new directory and any intermediate path

      "},{"location":"neovim-basics/file-buffer-window-tab/#swap-file","title":"Swap file","text":"

      Neovim creates a swap file, .swp, containing the changes made in a buffer to minimise loss should there be an issue with the computer or Neovim. Changes are written to the swap file after 200 characters or after 4 seconds pause.

      Swap file location

      :swapname shows the full path to the swap file for the current buffer, e.g.

      /home/practicalli/.local/state/astronvim/swap//%home%practicalli%projects%practicalli%books%neovim%docs%neovim-basics%files-buffers-windows.md.swp`\n

      :preserve command will write all text from current buffer to the swap file.

      :recover command overwrites the current buffer with the data from the swap file. :recover! command must be use if the buffer has newer changes than the swap file. Add a filename after the command to recover to a different file than that contained in the current buffer.

      Opening a file checks if there is an associated swap file and prompts the user

      • (A)bort opening the file
      • (D)elete the swap file
      • (E)dit anyway, select if the file is newer than the swap file
      • (R)ecover the data in the swap file into the file buffer

      :edit after the file is open also prompts if there is a swap file. Selecting (D)elete will delete the swap file without changing the current buffer

      "},{"location":"neovim-basics/file-buffer-window-tab/#buffers","title":"Buffers","text":"AstroNvimPracticalli Neovim Config Redux

      SPC f b selector for currently open buffers

      SPC b b to select a buffer from the tab line, pressing the character that appears next to the buffer tab (case sensitive)

      SPC b D to delete a buffer from the tab line, pressing the character that appears next to the buffer tab (case sensitive)

      Open multiple buffers when starting Neovim by specifying multiple files to open

      astro README.md deps.edn src/practicalli/playground.clj test/practicalli/playground.clj\n

      SPC b b switch between buffers in the current window, using a Telescope popup that lists all current buffers (includes files, Conjure REPL Log, etc.).

      SPC b n (:next) and SPC b n (:previous) to cycle through buffers in the current window

      SPC TAB (C-^) opens the previous buffer, useful to toggle between two buffers in the same window

      Use Telescope to switch between buffers

      Open multiple buffers when starting Neovim by specifying multiple files to open

      nvim README.md deps.edn src/practicalli/playground.clj test/practicalli/playground.clj\n
      "},{"location":"neovim-basics/file-buffer-window-tab/#buffer-text-wrapping","title":"Buffer text wrapping","text":"

      The test in a buffer is not wrapped by default. Set and unset soft text wrapping in a buffer

      AstroNvimPracticalli Neovim Config ReduxNeovim

      SPC u w toggles wrapping of text

      line wrap not enabled in configuration by default.

      fnl/config/init.fnl
      (nvim.ex.set :nowrap)\n

      :set wrap to set soft wrapping on current buffer

      :set nowrap to show lines in full (scroll sideways to see lines longer than the window)

      "},{"location":"neovim-basics/file-buffer-window-tab/#windows","title":"Windows","text":"

      Windows can be active (contains the cursor), hidden (open but not shown) or inactive.

      AstroNvimPracticalli Neovim Config ReduxNeovim

      \\ creates an horizontal split

      SPC q removes the current split

      SPC h / SPC l to jump to left / right buffer, SPC j / SPC k to jump to buffer below / above

      SPC b b to list current buffers and switch between them using telescope

      C-w and hjkl to navigate windows is the classic Vim approach

      C-w menu to manage Windows, also known as splits.

      C-w with one of hjkl will move the cursor to the next window in that direction. Also works with arrow keys.

      C-w w toggle between open windows

      :q or C-w q closes the active window, closing Neovim if it is the last active window.

      :wincmd can be used as an alternative to the Normal mode key bindings

      Open file in a new window

      :sp relative-or-full-filename-path\n

      Resize windows

      C-w -, +, < or > for vertical or horizontal size adjustment

      "},{"location":"neovim-basics/file-buffer-window-tab/#tab-pages","title":"Tab pages","text":"

      A Tab page can hold one or more tabs and are useful for grouping different types of files and information.

      A Tab page holds one or more windows, each window is a view on a buffer, a buffer holds the contents of a file or any other information in the editor memory (repl log, etc).

      A tab page can provide a logical grouping of windows, e.g. Clojure source code in one tab, tests in a second tab and REPL log in a third.

      Neovim window commands may be constrained within the bounds of a tab page (without using the :tab modifier)

      Tab pages are often referred to as tabs.

      AstroNvim

      g Tab jump to previously selected tab, commonly used to toggle between two tabs (Practicalli AstroNvim mapping)

      g t jump to next tab page

      g T jump to previous tab page

      "},{"location":"neovim-basics/multi-modal-editing/","title":"Multi-modal Editing","text":"

      TODO: Add multi-modal editing in Neovim guide

      Practicalli Spacemacs has useful reference content on multi-modal editing (Evil mode).

      Most of this content is the same in Neovim with a few exceptions

      "},{"location":"neovim-basics/multi-modal-editing/#selecting-text","title":"Selecting text","text":"

      vi) selects all the text within (), e.g. (http://oldwebsite.doh)

      "},{"location":"neovim-basics/multi-modal-editing/#surround","title":"Surround","text":"

      viw selects the current word, using j/k to modify the selection where required. o toggles which end of the selection is expanded/shrunk

      s substitues the selection, type the characters to surround the selection.

      p to pase the original text

      "},{"location":"neovim-basics/multi-modal-editing/#nvim-surround","title":"nvim-surround","text":"

      nvim-surround provides enhancments over the neovim surround command.

      nvim-surround included in Practicalli AstroNvim Config

      "},{"location":"neovim-basics/multi-modal-editing/#visual-mode","title":"Visual Mode","text":"

      viw to select the current word (visual in word)

      S on a visual selection to surround with next that character, e.g. S) to surround with parens.

      Closing paren surrounds without spaces

      ), ], } surrounds the selected text without spaces between the text and the open and closing parens.

      (, [, { surrounds the selected text with a space between the text and the open and closing parens.

      "},{"location":"neovim-basics/multi-modal-editing/#normal-mode","title":"Normal mode","text":"

      cs inside an existing pair of characters to change them to another pair of surrounding characters, e.g. cs(} to change (text) to {text}

      ds inside a pair of surrounding characters to delete them, e.g. ds( to change (text) to text

      ys you surround followed by motion and character, e.g. ysw) surrounds word with (parens)

      yS to surround current line

      ySS to surround current line, placing characters on new lines, e.g. ySS{ will change \"Olical/conjure\" to:

      {\n    \"Olical/conjure\"\n}\n

      The three \"core\" operations of add/delete/change can be done with the keymaps ys{motion}{char}, ds{char}, and cs{target}{replacement}, respectively. For the following examples, * will denote the cursor position:

      Old text                    Command         New text\n----------------------------------------------------\nsurr*ound_words             ysiw)           (surround_words)\n*make strings               ys$\"            \"make strings\"\n[delete ar*ound me!]        ds]             delete around me!\nremove <b>HTML t*ags</b>    dst             remove HTML tags\n'change quot*es'            cs'\"            \"change quotes\"\n<b>or tag* types</b>        csth1<CR>       <h1>or tag types</h1>\ndelete(functi*on calls)     dsf             function calls\n

      Neovim help provides details on using nvim-surround

      :help nvim-surround.usage\n
      "},{"location":"neovim-basics/multi-modal-editing/#web-links","title":"Web Links","text":"

      g x on a URL to open in the default browser

      "},{"location":"neovim-basics/multiple-cursors/","title":"Multiple cursors","text":"

      Visual-Multi (VM) is a multiple selections/cursors plugin that uses modal editing and provide visual feedback when editing multiple lines simultaneously.

      Mulitple cursors is generally useful when editing smilarly structured lines with diffferent content. Cursors are moved by column position or by using vim motions.

      AstroNvimPracticalli Neovim Config Redux

      Space g m opens the visual-multi menu

      Using visual select is a simple way to edit multiple lines as the same time.

      Space g m c on visually selected lines creates a cursor on each line.

      Esc or f d to close multiple cursors.

      \\ \\ c creates a cursor at the start of every visual selection line

      \\ \\ \\ toggle cursor at position

      "},{"location":"neovim-basics/multiple-cursors/#search-and-replace","title":"Search and Replace","text":"

      Select a text pattern to search for and use multiple cursors to add a cursor each match, then change each selected occurance concurrently.

      AstroNvimPracticalli Neovim Config Redux

      Select the pattern with visual select

      Space g m a on each matching text pattern in the buffer

      c to change the original text and type the new pattern. All cursors will update concurrently.

      Esc or f d to close multiple cursors.

      \\ \\ a creates a cursor at the start of every visual selection line

      \\ \\ \\ toggle cursor at position

      c to change the original text and type the new pattern. All cursors will update concurrently.

      Esc or f d to close multiple cursors.

      "},{"location":"neovim-basics/multiple-cursors/#command-quick-reference","title":"Command quick reference","text":"

      :help g:VM_maps for a reference of all mappings and instructions on how to change them

      AstroNvim

      SPC g m opens the Visual-Multi menu in normal mode

      g m opens the Visual-Multi menu in visual mode

      c to add a cursor to every line in the visually selected region

      Practicalli Neovim Config Redux

      \\ \\ is the leader for multiple cursors and will show the visual-multi menu in which-key.

      These commands cover the large majority of use cases for multiple cursors.

      Action Key Command Add Cursor at Position \\\\\\ vm-add-cursor Alignm VM cursors with cursor \\\\a vm-align Select All Words \\\\A vm-select-all Transposition \\\\t vm-transpose Toggle Mappings \\\\<Space> vm-mappings-toggle Find with Regex \\\\/ vm-regex-search Reselect Last \\\\gS vm-reselect-last

      Once visual-multi has started the vm-mappings-buffer mappings are available:

      Action Key Command Find Word <C-n> vm-find-word Next/Previous/Skip n / N / q vm-find-next Remove Region Q vm-remove-region Add Cursors Down/Up <C-Down> / <C-Up> vm-add-cursors Select Right/Left <S-Right>, <S-Left> vm-shift-select Slash motion g/ vm-slash Select Operator s vm-select-operator Find Operator m vm-find-operator

      NOTE: C-n conflicts with the Termux binding for naming a session

      "},{"location":"neovim-basics/multiple-cursors/#searching","title":"Searching","text":"

      g/ to search for a match to add when visual-multi is active, rather than the usual / vim search.

      n and N can't be used to repeat the search, as they are used to get the next visual-multi match.

      "},{"location":"neovim-basics/multiple-cursors/#find-with-regex","title":"Find with Regex","text":"

      \\ \\ / followed by a regex pattern will create a selection with that pattern.

      n and N finds the next occurrence of the regex pattern

      "},{"location":"neovim-basics/multiple-cursors/#smart-case-change","title":"Smart case change","text":"

      gc In extend-mode will use smartcase to change a selection

      • at main cursor, text is always inserted as typed
      • at other cursors, if region text was fully uppercased, replacement will be uppercased as well
      • if the region text was capitalized, the replacement will be as well
      "},{"location":"neovim-basics/multiple-cursors/#filter-regions","title":"Filter regions","text":"

      \\ \\ f filter out (remove) regions based on pattern or expression.

      C-x to cycle filtering method:

      • pattern: remove regions that don't match the pattern
      • !pattern: remove regions that match the pattern
      • expression: remove regions that don't match the expression (same as below)
      "},{"location":"neovim-basics/multiple-cursors/#transform-regions-with-expression","title":"Transform regions with expression","text":"

      \\ \\ e to transform a region with a vim expression, run on each region

      Placeholders can be used in the expression

      • `%t~ region's text as a string (as-is)
      • `%f~ region's text evaluated as a floating point number
      • `%n~ region's text evaluated as an integer number
      • `%i~ region's index
      • `%N~ total number of regions

      Examples: - %f * 0.5 divide text of all regions by 2 - %t .\" \". %i .\" / \". %n append index / total to text of each region - %i%2 ? %t : toupper(%t) uppercase all odd regions (1,3,5...) - %i%3-2 ? %t : '' delete every third region

      "},{"location":"neovim-basics/multiple-cursors/#vm-motions","title":"VM Motions","text":"

      visual-multi supports vim motions although they behave differently as their result is dependent on the mode:

      • cursor mode will move cursors
      • extend mode motions extend selections

      Unless multiline-mode is enabled motions are restricted to the current line and cannot cross line boundaries

      Some object-motions and various-motions require multiline-mode and aliased to avoid conflict with VM mappings:

      vim VM~ Description / g/ to next match (for all regions) ( ( [count] sentences backward ) ) [count] sentences forward { { [count] paragraphs backward } } [count] paragraphs forward [( g( go to [count] previous unmatched '(' [{ g{ go to [count] previous unmatched '{' ]) g) go to [count] next unmatched ')' ]} g} go to [count] next unmatched '}'"},{"location":"neovim-basics/multiple-cursors/#vm-operators","title":"vm-operators","text":"

      Visual-Multi supports several operators by default:

      • y / d / c to yank / delete / change
      • gu / gU to change text case

      Visual-Multi uses its own registers that are lists of strings. One element for each region that is yanked/deleted.

      There is also built-in support for:

      • vim-surround e.g. ysiw( to enclose in parentheses
      • vim-abolish e.g. cr_ to change current word to snake case

      `:help g:VM_user_operators to disccover how to doefine other operators

      "},{"location":"neovim-basics/multiple-cursors/#vm-multiline-mode","title":"vm-multiline-mode","text":"

      In normal and insert mode, cursors and selections are kept within their own line. Cursors are blocked from moving off the current line to the next line.

      M enables multiline-mode that allows cuursors to move onto another line.

      Multiline mode must be enabled for an object motions, or they will fail. See |vm-motions|.

      "},{"location":"neovim-basics/multiple-cursors/#alignment","title":"Alignment","text":"

      \\\\a aligns by setting the minimum column to the highest of all regions \\\\< aligns by character, or [count] characters \\\\> aligns by regex pattern

      In extend-mode selections are collapsed to cursors first, although will work regardless.

      "},{"location":"neovim-basics/multiple-cursors/#replace-pattern-in-regions","title":"Replace pattern in regions","text":"

      R to replace with a pattern and then the replacement text

      substitution will take place in all selected regions, leaving unselected text untouched.

      Only working in |extend-mode|. When |R| is pressed in |cursor-mode|, it will start |vm-replace-mode| instead.

      "},{"location":"neovim-basics/multiple-cursors/#subtract-pattern-from-regions","title":"Subtract pattern from regions","text":"

      \\\\s subtract the entered pattern from regions, splitting them. Only working in |extend-mode|.

      "},{"location":"neovim-basics/multiple-cursors/#transposition","title":"Transposition","text":"

      \\ \\ t swaps the contents of selections, cycling them if there are more than two.

      If there is an equal number of selections in each line, swapping takes place within the same line only. Only in |extend-mode|.

      "},{"location":"neovim-basics/multiple-cursors/#duplication","title":"Duplication","text":"

      \\ \\ d duplicates in place the contents of the selections, reselecting the original ones. Only in extend-mode.

      "},{"location":"neovim-basics/multiple-cursors/#shift-selections","title":"Shift Selections","text":"

      and move the selections right or left, preserving the surroundings."},{"location":"neovim-basics/multiple-cursors/#case-conversion","title":"Case conversion","text":"

      \\\\C runs on inner words in cursor mode

      • u lowercase
      • U UPPERCASE
      • C Captialize
      • t Title Case
      • c camelCase
      • P PascalCase
      • s snake_case
      • S SNAKE_UPPERCASE
      • - dash-case
      • . dot.case
      • <space> space case
      "},{"location":"neovim-basics/multiple-cursors/#modes","title":"Modes","text":"

      cursor-mode and extend-mode are two Visual-Multi modes, roughly corresponding to normal-mode and visual-mode

      TAB switches between cursor-mode and extended-mode

      "},{"location":"neovim-basics/multiple-cursors/#vm-cursor-mode","title":"VM Cursor Mode","text":"

      cursor-mode commands expect a motion, e.g. c should be followed by a text object to be changed.

      Key Description operators see vm-operators motions see vm-motions | set column for all cursors (to current column or [count]) r replace single character R enter vm-replace-mode ~ change case of single character & repeat last substitution <C-A> increase numbers <C-X> decrease numbers g<C-A> progressively increase numbers (v_g_CTRL-A) g<C-X> progressively decrease numbers (v_g_CTRL-X)

      You can enter |insert-mode| with i, I, a, A, and only from cursor mode also with o and O.

      Also see vm-motions for supported motions in VM (some with differences).

      "},{"location":"neovim-basics/multiple-cursors/#vm-extend-mode","title":"VM Extend Mode","text":"

      extend-mode is like having multiple visual selections. motions extend the slections and change / yank / delete commands don't wait for a motion, just like in visual mode.

      Even the key o works as in visual mode, inverting the anchor of the selections.

      Some commands are specific to |extend-mode|, such as:

      • s vim-surround
      • R replace pattern in regions
      • \\\\s split regions by pattern
      • \\\\e transform regions text with vim expression

      Some commands enforce cursor-mode when run from extend-mode:

      • <C-A> increase numbers
      • <C-X> decrease numbers

      Others can use a different mapping:

      • gu/gU change case (instead of vim u / U)
      • o and O mappings are used to invert the facing of the selected regions and not to start insert mode.
      "},{"location":"neovim-basics/notifications/","title":"Notifications","text":"Notifications only in AstroNvim, not the Practicalli Neovim configuration

      Space f n lists the history of notifications for the current sesion

      Enter to open the highlighted item in the list in its own popup

      Notification popups show information, warnings and errors.

      "},{"location":"neovim-basics/notifications/#configure-notifications","title":"Configure notifications","text":"

      Notifications are controlled by nvim-notify

      Practicalli astronvim-config overrides several default values in plugins/core.lua

      • top_down position of notifications, false shows popups from bottom of screen
      • timeout value controls how long a popup displays, defautl 3000
      • level of information displayed, level 3 hides less important information, e.g. file write messages, default 5

      Practicalli Configuration for notifications

      plugins/core.lua
        -- Configure notify popups\n  {\n    \"rcarriga/nvim-notify\",\n    opts = {\n      top_down = false,\n      timeout = 1000,\n      -- log level - 3 hide file write messages - default 5\n      level = 3,\n      -- background_color = \"#000000\",\n    },\n  },\n

      Noice uses nvim-notify configuration

      Noice replaces the UI for messages, command line and popup menus, although uses the configuration of nvim-notify for position and popup timing.

      "},{"location":"neovim-basics/plugin-manager/","title":"Plugin Manager","text":"

      Neovim community provides a wide range of plugins to greatly extend the features of Neovim

      There is a wide range of plugin managers too, including a built-in plugin manager in Neovim.

      • Lazy - AstroNvim Config
      • Packer - Practicalli Neovim Config Redux

      Lazy plugin manager recommeded

      Practicalli recommends Lazy plugin manager as it feels much easier to use and has a more engaing and understandable user interface

      Neovim evolving

      Neovim and its plugins are evolving quite rapidly, so it is recommended to update plugins if there are issue or when a newer version of Neovim has been installed

      Plugin issue are not that common and typically fixed quite quickly by the community

      AstroNvimPracticalli Neovim Config Redux

      Lazy plugin manager

      SPC P u to update packages to their latest versions (:PackerUpdate). Details of updated changes will be shown at the end of the update.

      r in the package update screen gives the option to revert an update if something has gone wrong (although this seem to be a rare issue).

      When packages are all at the latest available version, Packer update reports packages already up to date.

      Packer downloads packages and documentation from the Internet, so a connection is required

      "},{"location":"neovim-basics/plugin-manager/#package-list-and-documentation","title":"Package List and documentation","text":"AstroNvimPracticalli Neovim Config Redux

      Lazy plugin manager

      SPC P l to list the current packages added to the configuration

      Selecting a package will display the website documentation for the package (although this may be in HTML so not the cleanest way to read the docs).

      "},{"location":"neovim-basics/plugin-manager/#adding-packages","title":"Adding packages","text":"AstroNvimPracticalli Neovim Config Redux

      Lazy plugin manager

      Add package names as keywords in the use expression in fnl/config/plugin.fnl file.

      :requires to add a package that is a dependency for the package being added

      :mod defines the namespace that contains the package configuration, typically a setup function with options. The namespace matches the file name under fnl/config/plugin

      SPC P i to install packages that have been added to fnl/config/plugin.fnl

      q to quit once all packages are up to date

      "},{"location":"neovim-basics/search-replace/","title":"Search and Replace","text":"

      :substitute or :s vim command will highlight the matches for a text pattern and substitute for a new pattern

      Built-in help for the command

      :help :substitute\n
      Multiple cursors can also be used for multiple substitutions

      multiple cursors created on each occurance can be used to search and replace a pattern

      Subsitute the first matching patterns in the current line

      :s/current-pattern/new-pattern\n

      Subsitute all the matching patterns in the current line, g representing all occurances in a line

      :s/current-pattern/new-pattern/g\n

      Use % to specify the current buffer as the scope to change all matches

      :%s/current-pattern/new-pattern/g\n
      "},{"location":"neovim-basics/snippets/","title":"Snippets","text":"

      LuaSnip can use several different sources for snippets

      • VSCode JSON snippets (Friendly-snippets)
      • LSP style snippets
      LuaSnip Configuration

      Practicalli Neovim Config Redux includes the LuaSnip package which also adds friendly-snippets and cmp_luasnip.

        ; snippets\n  :L3MON4D3/LuaSnip \n  {:requires [:rafamadriz/friendly-snippets\n              :saadparwaiz1/cmp_luasnip]\n   :mod :lua-snip}\n
      Configure LSP snippet locations
      {\n  \"name\": \"practicalli-snippets\",\n  \"engines\": {\n    \"vscode\": \"^1.11.0\"\n  },\n  \"contributes\": {\n    \"snippets\": [\n      {\n        \"language\": [\n          \"markdown\",\n          \"global\",\n          \"all\"\n        ],\n        \"comment\": \"snippets accross several languages\",\n        \"path\": \"./global.json\"\n      },\n      {\n        \"language\": \n          \"markdown\",\n        \"path\": \"./markdown.json\"\n      }\n    ]\n  }\n}\n

      "},{"location":"neovim-basics/snippets/#snippet-definitions","title":"Snippet Definitions","text":"

      snippets directory contains snippet definitions, with a JSON file for each language, e.g. markdown.json

      Practicalli Neovim Config Redux contains several groups of snippet definitions

      • MkDocs format and icons (markdown.json VSCode syntax)

      Restart Neovim to load new defintions

      Snippets added to VSCode JSON snippets are only loaded when Neovim starts, so newly added snippets will only be available after Neovim is restarted.

      "},{"location":"neovim-basics/terminal/","title":"Terminal","text":"

      akinsho/toggleterm.nvim plugin provides a terminal session within Neovim, using a float, split or tab.

      AstroNvim

      SPC t for the Terminal sub-menu

      SPC t f opens a terminal in a floating window, useful for one-off commands

      SPC t h opens a terminal in a horizontal split, useful for a process that prints valuable feedback, e.g. a test runner in watch mode

      SPC t t opens a terminal in a tab, useful for background processes that do not need attendtion

      "},{"location":"neovim-basics/zen-mode/","title":"Focus Modes","text":"

      Focus on the code or text being created, without distractions

      zZ toggles Zen mode

      SPC z a ataraxis focus mode

      SPC z f focus current buffer

      SPC z n narrow to current buffer

      SPC z n remove status bar and window decorations

      v SPC z n narrow to selection

      "},{"location":"neovim-basics/zen-mode/#zen-mode","title":"Zen Mode","text":"

      Zen Mode distraction-free coding for Neovim

      Available via the Astrocommunity repository.

      Zen Mode configuration for AstroNvim

      .config/astronvim-config/plugins/community.lua
        { import = \"astrocommunity.editing-support.zen-mode-nvim\" },\n  {\n    \"folke/zen-mode.nvim\",\n    opts = {\n      -- override default configuration\n      -- https://github.com/folke/zen-mode.nvim#%EF%B8%8F-configuration\n      plugins = {\n        options = {\n          enabled = true,\n        },\n        kitty = {\n          enabled = true,\n          font = \"+4\", -- font size increment\n        },\n      },\n    },\n  },\n

      kitty configuration enables Zen Mode to resize kitty fonts.

      "},{"location":"neovim-basics/zen-mode/#true-zen","title":"True Zen","text":"

      true-zen.nvim clean and elegant distraction-free writing for NeoVim

      True Zen Mode configuration for AstroNvim

        {\n    \"Pocco81/true-zen.nvim\",\n    lazy = false,\n    opts = {\n      integrations = {\n        kitty = {\n          -- increment font size in Kitty.\n          enabled = true,\n          font = \"+4\",\n        },\n      },\n    },\n  },\n

      See kitty configuration to enable Zen Mode to resize kitty fonts.

      "},{"location":"neovim-basics/zen-mode/#kitty-configuration","title":"Kitty configuration","text":"

      Add allow_remote_control socket-only and listen_on unix:/tmp/kitty to the kitty config

      Kitty support for Zen Mode

      .config/kitty/kitty.config
      # ---------------------------------------------------------\n#  Neovim zen-mode-nvim\n#  - change the font size on kitty when in zen mode\nallow_remote_control socket-only\nlisten_on unix:/tmp/kitty\n# ---------------------------------------------------------\n
      "},{"location":"reference/lua-language/","title":"Lua","text":"

      Lua is the default language for Neovim configuration.

      "},{"location":"reference/lua-language/#learning-lua","title":"Learning Lua","text":"

      Neovim Lua introduction

      Lua.org - Programming in Lua (first edition)

      Codecademy - learn lua course

      "},{"location":"reference/lua-language/#reference","title":"Reference","text":"

      Lua.org 5.4 Reference Manual

      "},{"location":"reference/neovim/","title":"Neovim Reference","text":"
      • Language Providers
      • Key mappings
      "},{"location":"reference/neovim/language-providers/","title":"Language Providers","text":"

      Neovim delegates some features to language providers.

      :checkhealth command in Neovim shows if the binaries and tools required by each provider are available in the operating system.

      Resolve the issue with providers that generate a warning in the checkhealth report, following the ADVICE steps provided.

      "},{"location":"reference/neovim/language-providers/#disable-language-providers","title":"Disable Language Providers","text":"

      If a language is not used with Neovim, then its provider can be disabled. Details on how to disable a provider are included at the end of the ADVICE in the report section for that provider.

      Disable language providers in the init.lua configuration file

      init.lua
      -- Disable Language providers\nvim.g.loaded_node_provider = 0       --- (1)!\nvim.g.loaded_perl_provider = 0\nvim.g.loaded_python3_provider = 0\nvim.g.loaded_ruby_provider = 0\n
      1. Example configuration to disable providers is provided in the practicalli/neovim-config-redux configuration

      Ignore Language Provider warnings

      If the programming language is not used, there are no issues with using Neovim if the warnings are simply ignored

      "},{"location":"reference/neovim/standard-path/","title":"Neovim Standard Path","text":"

      View the standard paths used by Neovim using the help menu

      :help standard-path\n

      Output of command

      Standard Paths                  *standard-path*\n\nNvim stores configuration, data, and logs in standard locations. Plugins are\nstrongly encouraged to follow this pattern also. Use |stdpath()| to get the\npaths.\n\n                        *base-directories* *xdg*\nThe \"base\" (root) directories conform to the XDG Base Directory Specification.\nhttps://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\nThe $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_RUNTIME_DIR, and $XDG_STATE_HOME\nenvironment variables are used if defined, else default values (listed below)\nare used.\n\nCONFIG DIRECTORY (DEFAULT) ~\n                  *$XDG_CONFIG_HOME*            Nvim: stdpath(\"config\")\n    Unix:         ~/.config                   ~/.config/nvim\n    Windows:      ~/AppData/Local             ~/AppData/Local/nvim\n\nDATA DIRECTORY (DEFAULT) ~\n                  *$XDG_DATA_HOME*              Nvim: stdpath(\"data\")\n    Unix:         ~/.local/share              ~/.local/share/nvim\n    Windows:      ~/AppData/Local             ~/AppData/Local/nvim-data\n\nRUN DIRECTORY (DEFAULT) ~\n                  *$XDG_RUNTIME_DIR*            Nvim: stdpath(\"run\")\n    Unix:         /tmp/nvim.user/xxx          /tmp/nvim.user/xxx\n    Windows:      $TMP/nvim.user/xxx          $TMP/nvim.user/xxx\n\nSTATE DIRECTORY (DEFAULT) ~\n                  *$XDG_STATE_HOME*             Nvim: stdpath(\"state\")\n    Unix:         ~/.local/state              ~/.local/state/nvim\n    Windows:      ~/AppData/Local             ~/AppData/Local/nvim-data\n\nNote: Throughout the user manual these defaults are used as placeholders, e.g.\n\"~/.config\" is understood to mean \"$XDG_CONFIG_HOME or ~/.config\".\n\nLOG FILE                    *$NVIM_LOG_FILE* *E5430*\nBesides 'debug' and 'verbose', Nvim keeps a general log file for internal\ndebugging, plugins and RPC clients. >\n    :echo $NVIM_LOG_FILE\nBy default, the file is located at stdpath('log')/log unless that path\nis inaccessible or if $NVIM_LOG_FILE was set before |startup|.\n
      "},{"location":"reference/vim-style/","title":"Reference: Learn Vim-style Editing","text":"

      Learning vim-style multi-modal editing takes time and practice. Most importantly this approach can be learned in stages, as insert mode is the same editing experience as using most other editors.

      Start with the multi-modal concept and the basics of navigation. Then adopt more normal and visual mode actions, including motions

      "},{"location":"reference/vim-style/#getting-started","title":"Getting started","text":"

      :Tutor runs the intteractive tutorial built into neovim (:help tutor also runs the tutor)

      vim adventures isa simple online game to teach you the fundamentals of vim-style editing.

      "},{"location":"reference/vim-style/#navigation-and-searching","title":"Navigation and searching","text":"
      • moving the cursor
      • Motions
      • line numbers
      • jumping around a buffer
      • search and replace
      "},{"location":"reference/vim-style/#text-wrangling","title":"Text wrangling","text":"
      • iedit
      • text case
      "},{"location":"reference/vim-style/#clojure-editing","title":"Clojure Editing","text":"

      traversing expressions structural editing

      "},{"location":"reference/vim-style/case/","title":"Modifying text case","text":"

      Convert Characters and regioins to upper or lower case text.

      "},{"location":"reference/vim-style/case/#toggle-case-with-visual-select","title":"Toggle case with visual select","text":"

      v to visually select a character or use the vim motion keys to select a region

      U to uppercase current character or selected region

      u to lowercase current character or selected region

      ~ to toggle the case of the text in the selected region

      . will repeat the previous selection size and case toggle

      "},{"location":"reference/vim-style/case/#toggle-case-menu","title":"Toggle case menu","text":"

      Toggle the current character using vim motion keys, without needing to select a region.

      g ~ opens the toggle case menu

      TODO: Add screenshot of g ~ toggle case menu

      g ~ ~ uppercase current line (also works for RET and maybe other none-menu characters, but not SPC)

      "},{"location":"reference/vim-style/case/#cheatsheet","title":"Cheatsheet","text":"
      • ~ Changes the case of current character
      • guu Change current line from upper to lower.
      • gUU Change current LINE from lower to upper.
      • guw Change to end of current WORD from upper to lower.
      • guaw Change all of current WORD to lower.
      • gUw Change to end of current WORD from lower to upper.
      • gUaw Change all of current WORD to upper.
      • g~~ Invert case to entire line
      • g~w Invert case to current WORD
      • guG Change to lowercase until the end of document.
      • gU) Change until end of sentence to upper case
      • gu} Change to end of paragraph to lower case
      • gU5j Change 5 lines below to upper case
      • gu3k Change 3 lines above to lower case
      "},{"location":"reference/vim-style/g-menu/","title":"Evil G menu","text":"

      g` in normal mode opens a menu of convenient utilities. Practicalli uses this menu to comment existing lines, jumping to top or bottom of the buffer and changing text case.

      "},{"location":"reference/vim-style/g-menu/#comment-lines-and-regions","title":"Comment lines and regions","text":"

      g c c will comment the current line using the buffer major mode comment character(s). A prompt will ask if no comment character is set for the major mode.

      g c with a selected region will comment all lines with the major mode comment character(s)

      "},{"location":"reference/vim-style/g-menu/#jumping-around","title":"Jumping around","text":"

      g g jumps to the top of the buffer, g G to the bottom of the buffer

      g d to jump to the source code of a function definition, g D to open that in a different window.

      g f to jump to file name under cursor (if file exists).

      "},{"location":"reference/vim-style/g-menu/#changing-text-case","title":"Changing text case","text":"

      g u to change the current character or selection to lowercase, g U for uppercase.

      Toggle case with ~

      ~ will toggle the case of the current character or selected region.

      "},{"location":"reference/vim-style/key-binding-reference/","title":"Evil Keybinding Reference","text":"

      An alphabetically ordered reference of Vim-style key bindings.

      "},{"location":"reference/vim-style/key-binding-reference/#normal-mode-keybindings","title":"Normal mode keybindings","text":"

      Keybindings available in Evil normal mode and the resultant state they leave you in.

      Keybinding Description State @ execute keyboard macro (defined by q) Normal ' jump to mark (defined by m) Normal a append text after cursor Insert a append text after cursor Insert A append text to end of sentence Insert b move cursor back to start of previous word Normal B move cursor back to start of last word Normal c change (use with modifier) Insert C change rest of sentence Insert d delete (use with modifier) Normal D delete rest of sentence Normal e end of word Normal E end of word (seems same as above) Normal f find character forward (type character) Normal F find character backward (type character) Normal fd Escape (press almost together) Normal g go menu Normal G Jump to end of buffer Normal h move cursor left Normal H move cursor to top of buffer Normal i insert text Insert I insert text at start of sentence Insert j Move cursor down a line Normal J Join next line to end of current Normal k Move cursor up a line Normal K spacemacs/evil-smart-doc-lookup Normal l Move cursor right Normal L Move cursor to bottom of buffer Normal m Create marker (next character is marker name) Normal M Jump to middle of buffer Normal n Next search occurrence Normal N Previous search occurrence Normal o New line below Insert O New line above Insert p Paste (after / below cursor) Insert P Paste (before / above cursor) Insert q Record keyboard macro attached to given character Normal Q undefined Normal r Replace character under cursor Normal R Replace character state Replace s Substitute character Insert S Substitute current line Insert t Find character forward - cursor before character Normal T Find character backward - cursor after character Normal u Undo last change (C-R to redo) Normal U Undefined Normal v Visual select Visual V Visual select sentence Visual w Jump cursor to start of next word Normal W Jump cursor to start of next word Normal x Delete character under cursor Normal X Delete character backwards Normal y y Yank (copy) line Normal Y Yank (copy) line Normal z Menu - code folding / keyboard macros Normal"},{"location":"reference/vim-style/motions/","title":"Motions","text":"

      Todo

      Moving the cursor around by context rather than individual characters

      "},{"location":"reference/vim-style/moving-around/","title":"Moving around the cursor","text":"

      Scrolling is quite inefficient in most editors and moving (jumping) the curor around is far more effective.

      Using the h j k l as a common part of navigation provides consistency and keeps fingers on the most convienient part of the keyboard.

      "},{"location":"reference/vim-style/moving-around/#moving-by-charater","title":"Moving by charater","text":"

      h j k l keys move the cursor once character or can be used with numbers to move further.

      • h move left (often used to move up a path or tree, e.g. a directory path)
      • j move down
      • k move up
      • l move right (often used to move down a path or tree, e.g. a directory path)
      "},{"location":"reference/vim-style/moving-around/#moving-with-numbers","title":"Moving with numbers","text":"

      : followed by a number then one of h j k l keys will move the cursor that number in the director of the key.

      3j will move 3 lines down the buffer (or to the end of the fuffer if there are fewer lines remaining)

      Using Relative line numbers showws how far each line is from the current line. The practicalli/neovim-config sets :relativenames true in fnl/config/init.fnl.

      42l moves 42 charaters to the right

      moving by motions avoids the need to count characters

      "},{"location":"reference/vim-style/moving-around/#moving-around-the-buffer","title":"Moving around the buffer","text":"

      g g to jump to the top of the current buffer

      G to jump to the bottom of the buffer

      z z moves the current line and cursor to the middle of the window

      z t moves the current line and cursor to the top of the window

      z t moves the current line and cursor to the bottom of the window (or as far as the buffer will move in the window)

      "},{"location":"reference/vim-style/narrowing/","title":"Narrowing","text":"

      Narrowing to a region enables vim commands to be applied to a specific part of the current buffer, rather than the whole buffer.

      Common examples include - replacing local variables within a specific function (avoiding affecting other function definitions)

      "},{"location":"reference/vim-style/narrowing/#nrrwrgn-plugin","title":"NrrwRgn plugin","text":"

      [NrrwRgn plugin] is inspired by the Narrowing feature of Emacs and means to focus on a selected region while making the rest inaccessible.

      SPC n r opens a select region in a new split window. The original buffer is protected from changes.

      :w to write changes in the narrowed window to the original buffer

      "},{"location":"reference/vim-style/narrowing/#commands","title":"Commands","text":"

      :NR - Open the selected region in a new narrowed window :NW - Open the current visual window in a new narrowed window :WR - (In the narrowed window) write the changes back to the original buffer. :NRV - Open the narrowed window for the region that was last visually selected. :NUD - (In a unified diff) open the selected diff in 2 Narrowed windows :NRP - Mark a region for a Multi narrowed window :NRM - Create a new Multi narrowed window (after :NRP) - experimental! :NRS - Enable Syncing the buffer content back (default on) :NRN - Disable Syncing the buffer content back :NRL - Reselect the last selected region and open it again in a narrowed window

      Appending ! to most commands opens the narrowed part in the current window instead of a new window.

      :WR! closes the narrowed window in addition to writing to the original buffer.

      "},{"location":"reference/vim-style/narrowing/#documentation","title":"Documentation","text":"

      :help NarrowRegion to view the documetation on the NrrwRgn plug use

      "},{"location":"reference/vim-style/narrowing/#attention","title":"Attention","text":"

      :NRM is described as experimental by the project readme.

      "},{"location":"reference/vim-style/speaking-vim/","title":"Learning to speak Vim","text":"

      Neovim is easier to learn and gain much more benefit from if you learn to speak commands as sentences.

      First learn some verbs:

      • c change
      • d delete
      • g go,
      • v visual select
      • y yank (copy)

      Then use those verbs with some modifiers

      • ' mark
      • { } beginning/end of paragraph
      • 0 start of line
      • ^ first non white-space character of line
      • $ end of line
      • a around
      • f find (includes character)
      • i inside a range (e.g. word, paren,)
      • s surround
      • t till (move just before specified character)

      Then learn the text objects you can apply verbs and modifiers too

      • b block/parentheses
      • p paragraph,
      • s sentence
      • t tag e.g. html/xml
      • w word
      "},{"location":"reference/vim-style/speaking-vim/#examples-of-speaking-vim","title":"Examples of speaking Vim","text":"

      Practice speaking evil with these examples

      Keybinding Description c i s change inside current sentence (change the whole sentence) c i \" change inside double quotes c f ) change from cursor to next ) character c s ' \" change by the surrounding single quotes with double quotes c t X change till the character X (not including X) c /foo change until the first search result of \u2018foo\u2019 d d delete current line D delete current line from cursor onward d i w delete inside the current word (delete word) v t SPC visual select till the next Space character v s ] visually select and surround with [] without spaces v s [ as above with [ ] with spaces between parens and content g v go to last visual selection (select last visual selection) v a p visually select around current paragraph v i w S \" visually select, insert around current word, and surround with quotes y y yank (copy) current line y w yank (copy) current word y @ a yank (copy) to mark a (m a creates a mark called a)"},{"location":"reference/vim-style/vim-quick-reference/","title":"Neovim Quick Reference","text":"

      A reference of the most common keybindings available in Vim Normal mode. Spacemacs DOCUMENTATION key bindings section contains full details

      . repeats the last keybinding sequence used in Vim Normal mode or a change made within a complete Vim Insert session.

      "},{"location":"reference/vim-style/vim-quick-reference/#moving-around","title":"Moving around","text":"

      In Normal mode you can keep your fingers resting on the main row of your keyboard to move around.

      Key action j move cursor down one line k move cursor up one line l move cursor right one character h move cursor left one character

      In menus such as helm you can move around using Ctrl and these keybindings. So C-j will move the cursor down one item in a menu.

      "},{"location":"reference/vim-style/vim-quick-reference/#navigating-the-current-line","title":"Navigating the current line","text":"Key Action f to next character (you specify) t to just before the next character ; repeat f or t search w start of next word W start of next word, white space delimited e end of current word b start of previous word W end of next word, white space delimited * to next matching symbol name $ end of current line 0 start of current line ^ start of non-whitespace % jump to matching parens or next closed paren"},{"location":"reference/vim-style/vim-quick-reference/#navigating-the-current-buffer","title":"Navigating the current buffer","text":"Key action gg start of buffer G end of buffer H move cursor to head of buffer M move cursor to middle of buffer L move cursor to bottom line of buffer C-u jump up half a page C-d jump down half a page } move cursor forward by paragraph or block { move cursor backward by paragraph or block ma mark a line in a file with marker \"a\" `a after moving around, go back to the exact position of marker \"a\" 'a after moving around, go back to line of marker \"a\" :marks view all the marks '' go to the last place you were [{ jump back to the \"{\" at the beginning of the current code block C-o jump back to previous cursor location (evil-jump-backwards) C-i Go to newer position in jump list (opposite of C-o) : 4 go to line 4"},{"location":"reference/vim-style/vim-quick-reference/#text-editing","title":"Text Editing","text":"

      The following commands put you into the Evil Insert state

      Key Action i insert state at cursor I insert state at start of line a append - insert state after cursor A append - insert state at end of line o new line after cursor O new line before cursor"},{"location":"reference/vim-style/vim-quick-reference/#return-to-normal-state","title":"Return to Normal state","text":"

      Regularly switch back to normal state should become common practice. As soon as you finish typing some new text, it should become second nature to go back to normal state.

      ESC or press fd keys in extremely quick succession.

      fd shortcut for Esc

      Using f d together is low risk as if you dont get it right it will either add the characters or try find the next d character (as f moves to the next character). Keep trying this key combination as once in normal state you can use u to undo any f d characters inserted.

      "},{"location":"reference/vim-style/vim-quick-reference/#copy-cut-paste-undo-redo","title":"Copy, cut, paste, undo, redo","text":"

      v in Vim normal mode changes to Visual select mode. Use the navigation keys or any other movement keys to select text to copy or cut.

      Key Action y copy (yank) selection and add to kill ring x delete character at point and add to kill ring X delete character before point and add to kill ring p paste (put) u undo Ctrl-r redo

      Undo tips

      Undo will revert the last action in normal mode or all the changes you made in insert state

      "},{"location":"reference/vim-style/vim-quick-reference/#replace-and-changing-text","title":"Replace and changing text","text":"Key Action r replace the character under cursor R replace multiple characters until ESC cw change word from cursor to end 4 c w change 4 words v (select) c change region v (select) d delete region v i w c change current word v i d delete current word d w delete from cursor to end of word C change from cursor to end of line D , d $ delete from cursor to end of line"},{"location":"reference/vim-style/vim-quick-reference/#delete-commands","title":"Delete commands","text":"Key Action de delete to end of word, not including space dw delete to end of word, including space d$ delete to end of line dd delete the current line 4 d w delete 4 words 4 d $ delete 4 lines to end dt delete to a character (not including character) dab delete a whole block / expression dib delete contents of a block / expression cab change all the block / expression cib change inner block contents / expression yab yank all block / expression yib yank inner block contents / expression"},{"location":"reference/vim-style/vim-quick-reference/#repeat-commands","title":"Repeat commands","text":"Key Action . repeat last command again <number> <cmd> repeat command a number of times

      The . keybinding will repeat the last command in normal mode or the last text edit in insert mode.

      Type a number before a command and that command will run that number of times.

      Inserting a comment border

      Use the number repeat to create a border of 42 ; characters.

      Type 42 to repeat the command 42 times

      Press i for insert mode

      Press ; as the character to repeat insert

      Press ESC or fd to leave insert mode and insert all 42 ; characters

      "},{"location":"reference/vim-style/vim-quick-reference/#transposing-swap","title":"Transposing / swap","text":"Key Description x p transpose the current character with the next character"},{"location":"reference/vim-style/vim-quick-reference/#comments-works-for-all-major-modes","title":"Comments - works for all major modes","text":"

      g c c to comment out the current line

      g c to comment out the currently selected region

      To comment multiple lines you can use the repeat command style, especially useful if you are using relative line numbers.

      g c 3 j will comment the current line and the following two lines below. Comment in reverse using g c 3 k.

      In Visual state, v, select the lines you wish to comment and use g c to comment all the marked lines. Partially marked lines are not commented.

      "},{"location":"reference/vim-style/vim-quick-reference/#managing-files","title":"Managing Files","text":"

      Files in practicalli/neovim-config can be managed with Telescope plugin, although the neovim commands can also be used

      SPC p t toggles a visual file explorer on as a leftmost window, providing a further way to navigate files and directories.

      Key Description SPC f f find existing file (from current local root of neovim) SPC f / copy file - save current buffer with a new file name SPC f b browse files - Esc to run commands SPC f b Esc r change file name of current buffer

      Telescope file browser opens in Insert mode to allow typing filenames, to narrow the results in the Telescope popup.

      "},{"location":"reference/vim-style/vim-quick-reference/#telescope-browser-commands","title":"Telescope browser commands","text":"

      SPC f b opens telescope browser which allows commands to be run over the current file or directory.

      Esc swiches the Telescope popup to normal mode, allowing commands to be used

      • c create file / directory (any missing parts of a path are created)
      • r rename a file / directory
      • R replace

      TAB selects files and directories, allowing for commands (i.e. rename) to be done in batch mode (acting on all selected files / directories)

      "},{"location":"reference/vim-style/vim-quick-reference/#working-with-buffers","title":"Working with Buffers","text":"

      To work with files in Neovim they are loaded into a Buffer.

      Buffers are displayed in a window and you can change the window to show any of the current buffers.

      SPC b displays the buffer menu and the most common commands include:

      Key Command Description SPC b b :Telescope buffers List current buffers SPC b d :bdelete Kill current buffer SPC b n :bnext Switch to next buffer SPC b p :bprevious Switch to previous buffer SPC b a :ball Switch to previous buffer"},{"location":"reference/vim-style/vim-quick-reference/#quit-or-restart-emacs","title":"Quit or Restart Emacs","text":"

      I recommend using the Spacemacs menu from normal mode to quit / restart Spacemacs.

      Key Action SPC q a Quit Neovim (blocked if unsaved change in buffers) SPC q q Quit buffer (blocked if unsaved change in buffers) SPS q Q Force quit of Neovim"},{"location":"reference/vim-style/vim-quick-reference/#external-commands","title":"External commands","text":"

      run external commands using :! followed by a command. For example:

      :!ls - run the ls command

      "},{"location":"reference/vim-style/vim-tips-for-developers/","title":"Vim editing for Clojure developers","text":"

      Vim keybindings that drive Vim editing tools that are common for developers

      "},{"location":"reference/vim-style/vim-tips-for-developers/#comments-and-commenting-code","title":"Comments and Commenting code","text":"
      • g c c comment line
      • g c c comment line
      • v (select) g c comment region
      • g c 9 j comment 9 lines from current, downwards
      "},{"location":"reference/vim-style/vim-tips-for-developers/#simulated-structural-editing-with-surround","title":"Simulated structural editing with surround","text":"Keybinding Description v s ] surround with [characters] without spaces v s [ surround with [ characters ] without spaces c s ( [ change surrounding from ( to [ c i ( change in ( c a ( change \u201caround\u201d ( % jump forwards to next paren, further % toggles between open and close parens. x p transpose characters (cut current, paste after)"},{"location":"reference/vim-style/vim-tips-for-developers/#moving-around-quickly","title":"Moving around quickly","text":"

      f to jump forward to a given character on the current line. F to jump backwards.

      zt, zz, and zb to pull the current line to the top/middle/bottom of the screen.

      [number] G jump to line number or :22 to jump to line 22

      :7j to jump 7 lines down

      gf jump to file name under the cursor - try this in the summary.md file

      "},{"location":"reference/vim-style/vim-tips-for-developers/#selection-find-and-replace","title":"Selection, find and replace","text":"

      viw to visual-select in (within) the current word

      "},{"location":"reference/vim-style/vim-tips-for-developers/#source-code-and-configuration-files","title":"Source code and configuration files","text":"

      g Dopen definition in another window

      = (code-aware indenting) operator. Nice with the ap (a paragraph) text object.

      C-] Jump to definition of keyword under the cursor

      "},{"location":"reference/vim-style/vim-tips-for-developers/#code-folding","title":"code folding","text":"

      zc and zo are useful to close and open folds, which can be a nice way of focusing on certain pieces of code.

      "},{"location":"reference/vim-style/vim-tips-for-developers/#transposing-characters-and-sections","title":"Transposing characters and sections","text":"

      x p simple transpose of the current and next character

      M-t transpose words before and after cursor position

      {, } motions jump to next and previous empty lines. This motion makes it simple to rearrange paragraphs

      { d } will kill the paragraph (or multiple paragraphs)

      { will jump to the start of the previous paragraph

      p pastes the killed paragraph before the current paragraph

      > and < (indent and dedent) operators, useful with the aforementioned }/{ motions.

      / ## multi-replace with iedit and narrowing /

      "},{"location":"reference/vim-style/visual-select/","title":"Visual Select","text":"

      Select characters, words, lines and regions with visual select. The background changes color to visually indicate which text is selected

      v enters visual select mode and can be with objects such as word w and locations such as end of line $.

      o to expand from left side of selection and O to expand from the right side of a selection, using the hjkl navigation keys

      "},{"location":"reference/vim-style/visual-select/#visual-line-selection","title":"Visual Line selection","text":"

      V to select by lines, using j and k.

      "},{"location":"reference/vim-style/z-menu/","title":"Evil Z menu","text":"

      z in normal mode opens a menu of convenient utilities

      "},{"location":"reference/vim-style/z-menu/#folding-code-comments-and-other-content","title":"Folding code, comments and other content","text":"

      Code folding is very useful for hiding different levels of detail, for example you could hide everything but the function names in a namespace, showing just the API for that namespace.

      Comments and documentation can be folded to help you focus on a specific part of the content.

      Key Description z a toggle fold of code, comment, section, etc. z A toggle all folds z c close fold z f create fold z M close all folds z o open fold z O open fold recursive (capital o) z r fewer folds z R open all folds z x update folds

      See narrowing for a focused approach to editing.

      "},{"location":"reference/vim-style/z-menu/#scrolling","title":"Scrolling","text":"

      Jump the current line to the center, top or bottom of the buffer.

      Key Description z b scroll the current line to bottom of buffer z t scroll the current line to top of buffer z z scroll the current line to center of buffer"},{"location":"reference/vim-style/z-menu/#spelling","title":"Spelling","text":"

      z = with the cursor on a word shows a list of possible spelling and similar words.

      Select a word using its number in tye list to repace the word under the cursor, or q to quit the spelling list.

      Key Description z = spelling suggestions z g add word to spelling list z w mark word as misspelled"},{"location":"repl-driven-development/","title":"REPL Driven Development with Clojure","text":"

      Conjure provides the REPL driven development workflow for Clojure (and many other fun languages) and includes a built-in tutorial.

      • Vim style Editing
      • Starting a REPL & Evaluating code - using Conjure
      • Structural Editing - parinfer or paredit
      • Refactor tools - Language Server Protocol features
      • Unit Testing - run REPL or external test runners
      • Inspecting data, e.g. Portal

      Practicalli Clojure CLI Config

      Practicalli Clojure CLI config contains aliases used extensively through the Clojure sections of this book

      Practicalli Neovim config replaces some key bindings

      practicalli/neovim-config-redux replaces several key bindings to make them consistent with other Clojure editors

      "},{"location":"repl-driven-development/#references","title":"References","text":"
      • Which Clojure CLI execution option - M T X P - should be used
      • Make task to simplify Clojure development
      "},{"location":"repl-driven-development/conjure/","title":"Conjure","text":"

      Conjure is the Clojure REPL client for Neovim. Code in source code buffers can be evaluated and show the results in-line, providing instant feedback on the behaviour of the code as it develops.

      Conjure School interative tutorial

      :ConjureSchool runs an interactive tutorial in Neovim, walking through the essential Conjure commands and key bindings. Use the commands provided to move through the guide or j / k to scroll through the guide content.

      "},{"location":"repl-driven-development/conjure/#start-repl","title":"Start REPL","text":"

      Start a REPL on the command line in the root of a Clojure project. The REPL should also start an nREPL server for Conjure to connect too.

      Conjure will detect an nREPL server (via .nrepl-port file) when a Clojure file is opended (.clj .edn .cljs .cljc) and connect to the REPL process via that nREPL server.

      Practicalli Clojure CLI ConfigManual Alias definition

      Practicalli Clojure CLI config contains aliases to start a REPL process that also start an nREPL server.

      Use repl make task for projects created by Practicalli Project templates

      make repl\n

      Or use the Clojure CLI command with the :repl/rebel alias directly

      clojure -M:repl/rebel\n
      Simplify the command line

      Add a Makefile to define common tasks to simplify and add consistency to working with Clojure across projects or shell script to simplify the commands used to call clojure to run common tasks

      repl:  ## Run Clojure REPL with rich terminal UI (Rebel Readline)\n    $(info --------- Run Rebel REPL ---------)\n    clojure -M:env/dev:env/test:repl/rebel\n\n\nrepl-reloaded:  ## Run Clojure REPL with hotload, reload and rich terminal UI (Rebel Readline)\n    $(info --------- Run Rebel REPL ---------)\n    clojure -M:env/dev:env/test:lib/reloaded:repl/rebel\n

      A Makefile can also include supporting commands, such as lint and format tools.

      # Run MegaLinter with custom configuration\nlint:\n    $(info --------- MegaLinter Runner ---------)\n    mega-linter-runner --flavor java --env 'MEGALINTER_CONFIG=.github/linters/mega-linter.yml'\n

      practicalli/dotfiles/Makefile contains tasks for Clojure development, including running a REPL, preparing dependencies, building an uberjar, lint & format Clojure and configuration files.

      Docker related tasks to build, run and compose common images and containers are also included.

      Add aliases to the user configuration for Clojure, e.g. XDG_HOME_CONFIG/clojure/deps.edn or HOME/.clojure/deps.edn

        ;; Interactive client REPL with nREPL server for Clojure Editor support\n  :repl/basic\n  {:extra-deps {nrepl/nrepl       {:mvn/version \"1.0.0\"}\n                cider/cider-nrepl {:mvn/version \"0.40.0\"}}\n   :main-opts  [\"--main\" \"nrepl.cmdline\"\n                \"--middleware\" \"[cider.nrepl/cider-middleware]\"\n                \"--interactive\"]}\n\n  ;; Headless REPL with nREPL server for Clojure Editor support\n  :repl/headless\n  {:extra-deps {nrepl/nrepl       {:mvn/version \"1.0.0\"}\n                cider/cider-nrepl {:mvn/version \"0.40.0\"}}\n   :main-opts  [\"--main\" \"nrepl.cmdline\"\n                \"--middleware\" \"[cider.nrepl/cider-middleware]\"]}\n

      clojure -M:repl/basic starts a REPL with nREPL with a minimal REPL UI

      clojure -M:repl/headless starts a REPL with nREPL server but without a REPL prompt (to prevent accidental interaction via the command line)

      Practicalli Clojure CLI Config aliases

      Practicalli Clojure CLI config defines aliases for a wide range of community tools and libraries that extend the features of Clojure CLI

      "},{"location":"repl-driven-development/conjure/#evaluation","title":"Evaluation","text":"

      Clojure REPL workflow encourages code expressions to be evaluated as the are written, providing instant feedback to ensure expected results are returned (or learn the kind of results a function returns).

      Results of evaluating an expression are shown in-line. Open the REPL log to see larger results and a complete REPL history for the current session.

      ,eb - evaluate current buffer - used after first starting the REPL to load in a whole namespace and any required namespaces. Use to ensure all changes have been evaluated in the REPL (except those within a (comment ) form or otherwise commented)

      ,er - evaluate top-level expression (root), ignoring a surrounding (comment ) form to support the rich comments approach

      ,ee - evaluate expression (from start of current form) - especially useful for nested forms

      ,ei - interrupt evaluation (stop long running evaluations) - stop a long running evaluation

      ,ew - evaluate word (symbol) - inspect value of form - i.e. for def names

      ,e! - replace form with its result - helps understand a more complex function by replacing code with a specific value

      ,emf - evaluate marked form - mark forms regularly re-evaluted with mf (or any character with m) to avoid jumping to that form each time . A capital letter to mark form in a different namespace and evaluate from the current buffer.

      \"cp - paste contents of the register into buffer. The result of every evaluation is stored in a Neovim register as well as the log.

      "},{"location":"repl-driven-development/conjure/#repl-log","title":"REPL log","text":"

      The Conjure REPL log shows the results of every evaluation for the current session.

      ,lt opens log in a new tab page (tab), ,ls in horizontal split, ,ls in vertical tab

      ,lq - close log window / tab page

      ,lr - soft REPL reset, leave window open

      ,lR - hard REPL reset, close window & delete buffer

      Inline evaluation over HUD log popup

      Practicalli Neovim configurations hide the HUD log popup that is otherwise shown when Conjure connects to the REPL process, i.e. vim.g[\"conjure#log#hud#enabled\"] = false

      In-line evaluation results are the main feedback approach used by Practicalli when evaluating code.

      Practicalli recommends using the REPL log when larger results are returned

      Portal data inspector can be sent evaluation history and provides rich visualisation and navigation tools to explore that history in detail.

      "},{"location":"repl-driven-development/conjure/#rich-comments","title":"Rich comments","text":"

      Rich comments are a useful way to contain experimental expressions, or expresisons only evaluated directly by a person developing the code (e.g. starting / stoping services, testing api calls, etc.)

      Expressions in rich comments are not included when evaluating the buffer or when expressions are evaluated via a namespace require.

      ,er to evaluate the top level form within the rich comment, without evaluating the comment expression itself.

      Start REPL from Neovim

      Practicalli Configurations require the vim-jack-in plugin to be added before this approach will work.

      Start Neovim with a Clojure file, e.g. nvim src/practialli/playground.clj or run nvim and open a Clojure file, e.g. *.clj, *.cljc, *.cljs or .edn.

      • :Clj command to start a REPL using Clojure CLI Tools
      • :Lein command to start a REPL using Leiningen

      Neovim switches to a terminal state, use C-\\ C-n to leave the terminal state. Use :N or :previous to switch back to the source code buffer

      , c f to connect to the REPL from Conjure, or simply open a Clojure file. Automated connection will be added in a future version on Conjure.

      The vim-jack-in plugin enables Neovim to call out to Clojure tools or Leiningen to start a REPL and connect to it once its started.

      A full screen REPL log is displayed. , l q to close the log window and return to the Clojure file. , l v to create a vertical split between code and REPL log, , l h for a horizontal split.

      "},{"location":"repl-driven-development/refactor-tools/","title":"Refactor tools","text":"

      Neovim and common plugins provide many text oriented tools useful for refactoring code.

      Clojure LSP server and Neovim LSP client support use static analysis of the project source code to allow provide common code refactor tools.

      "},{"location":"repl-driven-development/refactor-tools/#language-server-protocol-lsp","title":"Language Server Protocol (LSP)","text":"

      Using clojure-lsp server and Neovim Treesitter as an LSP client, code is statically analysed to provide auto-completion data, advanced editing actions such as refactor, live formatting, etc.

      Function help

      SPC l h or K displays help for the function under the cursor

      Repeat the key binding to move the cursor to the documentation popup window and use j k to scroll the documentation

      "},{"location":"repl-driven-development/refactor-tools/#key-maps","title":"Key maps","text":"Practicalli AstroNvim ConfigPracticalli Neovim Config Redux
      • <leader>la code actions (popup with available actions)
      • <leader>ld hover diagnostics
      • <leader>lD search diagnostics
      • <leader>lf format buffer
      • <leader>lG search workspace symbols
      • <leader>lh function signature help
      • <leader>li information about the LSP client and running LSP servers
      • <leader>lI null-ls information (format & lint tools)
      • <leader>ll code lens refresh
      • <leader>lL code lens run
      • <leader>lr rename current symbol (namespace rename not supported it seems)
      • <leader>lR search references
      • <leader>ls search symbols
      • <leader>lS symbols outline
      • gd Go to definition
      • K Show documentations
      • <leader>ld Function declarations
      • <leader>lt Type Definitions
      • <leader>lh Signature Help
      • <leader>ln Rename
      • <leader>le Show line diagnostics
      • <leader>lq Show all diagnostics information
      • <leader>lf Auto format
      • <leader>lj Go to next diagnostic
      • <leader>lk Go to previous diagnostic
      • <leader>la Open code actions menu (Using telescope plugin interface)
      • <leader>la Open code actions menu for the selected text in VISUAL mode (Using telescope plugin interface)
      • <leader>lw Open workspace diagnostics list (Using telescope plugin interface)
      • <leader>lr Show all references list for item under the cursor (Using telescope plugin interface)
      • <leader>lr Show all implementations list for item under the cursor (Using telescope plugin interface)
      "},{"location":"repl-driven-development/refactor-tools/#limitations-to-investigate","title":"Limitations to investigate","text":"
      • Neovim client does not seem to support namespace rename (AstroNvim)
      "},{"location":"repl-driven-development/structural-editing/","title":"Structural Editing","text":"

      Structural editing seeks to ensure that parenthesis (parens) and other pairs of characters remain balanced, i.e. an open paren is not removed without removing the closing paren.

      • parinfer uses an indent approach, aligning code manages parens locations
      • paredit uses a structural approach
      AstroNvim Community Clojure Pack includes parinfer

      nvim-parinfer plugin is included in the AstroNvim Community Clojure pack

        { import = \"astrocommunity.pack.clojure\" },\n
      "},{"location":"repl-driven-development/structural-editing/#parinfer","title":"Parinfer","text":"

      Parinfer works very well with vim-style modal editing.

      The author of the code focuses on aligning code and parinfer takes care of balancing the parens.

      To include new lines of code within an expression, create a new line o and indent.

      Parinfer will move the preceeding closing paren(s) to the new line, enclosing the new code in the overall expression.

      Parinfer website

      "},{"location":"repl-driven-development/testing/","title":"Unit tests and test runners","text":"

      Run unit tests from within Neovim, showing a summary of test results or a full test report (especially if there are failures)

      Or run and external test runner via a terminal session, optionally using watch mode to re-run tests on every saved change.

      Practicalli sets Kaocha test runner as default

      practicalli/neovim-config-redux sets Kaocha as the default test runner

      Kaocha test runner set in Astrocommunity Clojure language pack

      Astrocommunity Clojure language pack
      {\n  \"Olical/conjure\",\n  -- load plugin on filetypes\n  ft = { \"clojure\" },\n  init = function()\n    vim.g[\"conjure#log#hud#width\"] = 1\n    vim.g[\"conjure#log#hud#enabled\"] = false\n    vim.g[\"conjure#log#hud#anchor\"] = \"SE\"\n    vim.g[\"conjure#log#botright\"] = true\n    vim.g[\"conjure#extract#context_header_lines\"] = 100\n    vim.g[\"conjure#eval#comment_prefix\"] = \";; \"\n    vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#enabled\"] = false\n    vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#hidden\"] = true\n    vim.g[\"conjure#client#clojure#nrepl#connection#auto_repl#cmd\"] = nil\n    vim.g[\"conjure#client#clojure#nrepl#eval#auto_require\"] = false\n    vim.g[\"conjure#client#clojure#nrepl#test#runner\"] = \"kaocha\"\n\n    vim.api.nvim_create_autocmd(\"BufNewFile\", {\n      group = vim.api.nvim_create_augroup(\"conjure_log_disable_lsp\", { clear = true }),\n      pattern = { \"conjure-log-*\" },\n      callback = function() vim.diagnostic.disable(0) end,\n      desc = \"Conjure Log disable LSP diagnostics\",\n    })\n
      "},{"location":"repl-driven-development/testing/#include-test-path","title":"Include test path","text":"

      Ensure the test directory is included in the classpath when starting a REPL. Use a project or user level alias which defines an :extra-paths key with the [\"test\"] path

      clojure -M:test/env:repl/reloaded\n
      "},{"location":"repl-driven-development/testing/#conjure-test-runners","title":"Conjure Test runners","text":"

      , t n to run the tests for the current namespace

      , t a to run all tests in the project

      "},{"location":"repl-driven-development/testing/#external-test-runner","title":"External test runner","text":"

      Open a terminal in Neovim or a separate terminal session to run start a test runner in watch mode. Tests run automatically when the code changes are saved

      Practicalli Clojure CLI Config

      Practicalli Clojure CLI config contains aliases for test runner tools

      • :test/run uses Kaocha to run all tests, stopping on first failing test. Add :fail-fast? false argument to run all tests regardless of failure

      • :test/watch as above and puts Kaocha in watch mode, triggering a test run each time a file is saved

      Projects created with Practicalli Project Templates include a test and test-watch task to run Kaocha test runner

      Run all tests, stoping on first failing test

      make test\n

      Watch for changes and run all tests, stoping on first failing test

      make test-watch\n

      The make tasks call Clojure CLI with the appropriate alias, e.g. clojure -X:test/run and clojure -X:test/watch

      "},{"location":"repl-driven-development/testing/#test-selectors","title":"Test Selectors","text":"

      Use Test selectors to run a sub-set of tests based on selector meta data added to deftest code

      (deftest ^:infrastructure function-name-test\n  (testing \"\"\n    (is ,,,))\n\n(deftest ^:persistence function-name-test\n  (testing \"\"\n    (is ,,,))\n
      Kaocha test runnerCognitect Labs Test Runner

      Kaocha test runner can focus or skip on a sub-set of unit tests using test id, metadata, namespaces or a specific deftest.

      • :focus or :skip a given namespace or specific test var, i.e. deftest
      • :focus-meta or :skip-meta test selectors (metadata) on test vars, i.e. ^:persistence

      Specifying test :id in the tests.edn configuration file allows different test suites to be run, e.g. :unit for unit tests, :spec for specification tests

      Focus and skip works with a single test run or with a continuous watcher.

      Skip all tests with :persistence metadata

      clojure -X:test/watch :skip-meta :persistence\n

      Focus on a specific test namespace

      clojure -X:test/watch :focus '[\"practicalli.gameboard.api.scoreboard-test\"]\n

      Focus on a specific unit test (deftest)

      clojure -X:test/watch :focus '[\"practicalli.gameboard.api.scoreboard-test/total-score-test\"]\n

      Refine the tests that are watched

      Start the watcher with focused or skiped tests by name or meta data (test selectors)

      Cognitect Labs Test Runner can include or exclude a sub-set of tests, identified by metadata on the var (deftest)

      Cognitect Labs Test Runner - inclusions & exclusions

      "},{"location":"termux/","title":"NeoVim on Termux","text":"

      A smart phone or tablet and an external keyboard can make an excellent ultra-portable development environment, especially when travelling with limited space or restricted weight constraints.

      Termux can be installed using the F-Droid marketplace, as with installing any other Android app.

      Do not install Termux App from the Google Play store

      The Termux App in Google Play store is significantly out of date and will not work properly and probably not at all

      "},{"location":"termux/#keyboard","title":"Keyboard","text":"

      Atreus from Keyboardio is an excellent travel keyboard and was used to write most of the Practicalli Neovim configuration and Neovim book (especially during delays at airports)

      Model 100 from Keyboardio is used by Practicalli at the office

      "},{"location":"termux/#running-termux","title":"Running Termux","text":"

      Run Termux from the app launcher added as an android app by the F-Droid install.

      A help menu will show the basic command needed to work with packages.

      Software keys for Termux specific controls are shown. Keys can be toggled with the Volume Up + q key combination.

      Termux may not display in fullscreen when Android productivity mode, usually activated on tablets when physical keyboard or mouse is attached. Disabling productivity mode in the Android settings is recommended.

      "},{"location":"termux/clojure-development/","title":"Clojure development environment","text":"

      A comprehensive development environment for Clojure, supporting a REPL workflow and static analysis of code via Clojure Language Server Protocol.

      "},{"location":"termux/clojure-development/#java-host-platform","title":"Java host platform","text":"

      Install OpenJDK to host the Clojure REPL process and run packaged Clojure applications.

      Java 17 Long Term Support version is recommended as it is very stable, receives security updates and has the latest highly tested performance improvements.

      pkg install openjdk-17\n
      "},{"location":"termux/clojure-development/#install-clojure","title":"Install Clojure","text":"

      Clone practicalli/clojure-deps-edn to add a wide range of community tools to the Clojure CLI

      git clone git@github.com:practicalli/clojure-deps-edn.git ~/.config/clojure\n

      Use the Linux install with a prefix path pointing to Termux equivalent of /usr/local. Find the path using echo $PATH and remove bin from the end. Without the prefix Clojure will not install correctly

      curl -O https://download.clojure.org/install/linux-install-1.11.1.1149.sh\n\nchmod +x linux-install-1.11.1.1149.sh\n\n./linux-install-1.11.1.1149.sh --prefix /data/data/com.termux/files/usr/\n

      clojure binary is installed in the existing bin, lib and share directories in /data/..../usr/, placing that binary on the system execution path.

      Test by running a REPL session, for example with Rebel Readline

      clojure -M:repl/rebel\n

      optionally install rlwrap package if using the basic repl terminal UI

      "},{"location":"termux/clojure-development/#install-clojure-lsp","title":"Install Clojure LSP","text":"

      Visit clojure-lsp GitHub releases page and download the clojure-lsp file

      • visit the relases page in firefox and copy the link to the file.
      • use wget and paste the link to the file to download
      • make executable chmod 755 clojure-lsp
      • test locally ./clojure-lsp --version - should print clojure-lsp version and clj-kondo version
      • copy or move file to path mv clojure-lsp $PATH

      If the practicalli/clojure-lsp-config repository was cloned, move or link the clojure-lsp directory to ~/.config/clojure-lsp

      "},{"location":"termux/custom-shell/","title":"Customise shell","text":"

      Customising the shell is optional, although gives an enhanced experience.

      Zsh provides the richest command line experience, providing many advanced features over bash. Oh My Zsh is a community configuration that provides a simple way to configure Zsh features and also supports powerline10k terminal theme, providing context specific information and a more engaging visual experience.

      Oh My Zsh community configuration enhances the Zsh experience. Practicalli normally uses Prezto community configuration, unfortunately this did not work well on Termux.

      "},{"location":"termux/custom-shell/#install-zsh","title":"Install Zsh","text":"

      Install the zsh package using the Termux package manager

      pkg install zsh\n

      Start zsh, which will show a % character as the prompt

      zsh\n

      Set the shell to run zsh by default

      chsh -s zsh\n
      "},{"location":"termux/custom-shell/#install-oh-my-zsh","title":"Install Oh My Zsh","text":"

      Install Oh My Zsh via curl (or wget if preferred) in the .oh-my-zsh/ directory

      sh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\n
      "},{"location":"termux/custom-shell/#install-powerline10k","title":"Install Powerline10k","text":"

      Powerline10k is a visually appealing prompt with a setup script to visually choose the presentation of the prompt.

      git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k\n

      Edit ~/.zshrc and set the theme to ZSH_THEME=\"powerlevel10k/powerlevel10k\"

      nano ~/.zshrc\n

      Save the changes exit termux.

      Next time zsh is run, powerline10k setup script will run. If Meslo is not set as the terminal font, the setup script offers to install the font. Installing the font will restart Termux (without prompting for a restart).

      The powerline10k setup script provides a wizard to configure each part of the prompt.

      p10k configure command will manually run the powerline10k setup script.

      Prezto Zsh community configuration

      NOTE: previous attempts to use Prezto proved to have issues

      Clone prezto and its sub-modules into XDG_CONFIG_HOME/zsh which is typically ~/.config/zsh

      git clone --recursive https://github.com/sorin-ionescu/prezto.git \"${ZDOTDIR:-${XDG_CONFIG_HOME:-$HOME/.config}/zsh}/.zprezto\"\n

      Set the location of the Zsh configuration home with $ZDOTDIR, relative to the XDG locations

      export ZDOTDIR=\"${ZDOTDIR:=$XDG_CONFIG_HOME/zsh}\"\n

      Create a new Zsh configuration by copying/linking the Zsh configuration files provided:

      setopt EXTENDED_GLOB\nfor rcfile in \"${ZDOTDIR:-$HOME}\"/.zprezto/runcoms/^README.md(.N); do\n  ln -s \"$rcfile\" \"${ZDOTDIR:-$HOME}/.${rcfile:t}\"\ndone\n

      Practicalli Zsh configuration

      Clone practicalli/dotfiles and replace the symbolic links in $XDG_CONFIG_HOME/zsh with links to the respective Zsh configuration files in the cloned repository (or copy the files if you prefer)

      Copy or create a symbolic like for the .p10k configuration or skip this to create your own configuration when next startingzsh.

      Edit $XDG_CONFIG_HOME/.config/zsh/.zshenv and add the following lines to enable zsh to find the prezto configuration

      export XDG_CONFIG_HOME=\"${XDG_CONFIG_HOME:=$HOME/.config}\"\nexport ZDOTDIR=\"${ZDOTDIR:=$XDG_CONFIG_HOME/zsh}\"\n

      Create a symbolic link from $HOME/.zshenv to $XDG_CONFIG_HOME/.config/zsh/.zshenv (or to the .zshenv file from practicalli/dotfiles)

      ln -s $XDG_CONFIG_HOME/.config/zsh/.zshenv $HOME/.zshenv\n

      Check the configuration is working by loading the .zshenv configuration

      source \"$ZDOTDIR/.zshenv\"\n

      Using Oh My Bash

      If preferring Bash, then ohmybash provides a nice command line experience, showing completions clearer, nice themes that provide information.

      "},{"location":"termux/fdroid-install/","title":"Install Termux via the F-Droid App","text":"

      Visit the FDroid app website and download F-Droid, which saves an F-Droid.apk file. Android may display a security prompt stating the browser does not have permissions to install software. The popup should include a configure link that opens the Android settings to allow the browser to install software.

      F-Droid should now be installed and its icon added to the Android system alongside all other Android apps.

      Open the F-Droid App and allow it to update its repositories, to ensure the latest list of apps are shown.

      Search for the Termux application, clicking on the Termux name if more details are required.

      Select the Install button.

      When installing apps from F-Droid for the first time, a security prompt is show as F-Droid is an unknown source.

      Select Settings to open the Android settings and enable Allow from this source for the F-Droid app.

      A Termux App launcher will be added to the Android screen. Consider adding the Termux icon to the commonly used icons bar.

      "},{"location":"termux/fdroid-install/#termux-styling","title":"Termux Styling","text":"

      Styling is a Termux plugin that provides a visually richer experience. Styling contains beautiful color schemes and powerline-reaqdy fonts to customise the appearance of the terminal

      Install the package via F-Droid

      The styling menu is accessed via a long press on the Termux app screen, showing a More > Style option on the usual cut/copy/paste popup menu. Practicalli recommends FiraCode fonts.

      "},{"location":"termux/git/","title":"Git version control","text":"

      A Git client is used to version control projects and to clone projects and configuration from GitHub/GitLab. Practicalli maintains several editor configurations in shared repositories on GitHub

      • Install a Git Client (and optionally GitHub CLI)
      • [optionally] clone the practicalli/dotfiles repository for the Git config and global ignores
      • Configure an SSH key to access remote repositories (or Developer token if you cannot use SSH keys)
      "},{"location":"termux/git/#install-a-git-client-and-github-cli","title":"Install a git client and GitHub CLI","text":"
      pkg install git gh\n

      Clone the practicalli/dotfiles repository

      git clone https://github.com/practicalli/dotfiles projects/dotfiles\n

      Move or symbolically link the top-level directories to ~/.config, e.g. for the Git configuration:

      ln -s projects/dotfiles/git ~/.config/git\n

      Edit the .config/git/config and update the core.user, core.name and github or gitlab identities

      "},{"location":"termux/git/#create-ssh-key-for-remote-repository-access","title":"Create SSH Key for remote repository access","text":"

      Install the openssh package which contains the ssh-keygen command to generate a new public/private key combinations for use with GitHub SSH repository URLs

      pkg install openssh\n

      Generate a key using the email address of the GitHub or GitLab account

      ssh-keygen -t rsa -C name@domain.tld\n

      RET to confirm storing the keys in the default location.

      Usually a passphrase is recommended, however, termux does not seem to save a keyring to save the key passphrase using ssh-add. So the passphrase must be entered each time the key is used, unless a blank passphrase is used.

      Vist your GitHub account settings and create a new SSH key

      Use cat ~/.ssh/id_rsa.pub to show the public key contents. Press the screen to select and copy the public key to the clipboard.

      Paste the public key into the GitHub new key form.

      "},{"location":"termux/git/#optional-create-a-developer-token","title":"[optional] Create a developer token","text":"

      A developer token (or ssh key) is required to access GitHub {and far more secure over password}

      Should the android device become lost or compromised, the developer token can be deleted to protect the repositories from any malicious access. The developer token should be limited to the minimal access. The developer token does not give access to the GitHub or GitLab account.

      HTTPS URLs should be used with a developer token. git@git.com URLs are for SSH keys only.

      Visit GitHub / GitLab settings for your account

      Create a new developer token specifically for Termux

      Add a descriptive name for the token, based on the device Termuxc is runniung on, e.g. Termux Pixel2XL

      Check the public_repo and status repo scopes

      Generate button creates a new token.

      Copy the token using the copy icon.

      Edit the .config/git/config file and add a github section with the GitHub account name and token

      [github]\n    name = practicalli\n    token = ghp_************************************\n

      Consider using GitHub CLI to cache the developer token rather than write the token to the Git configuration file for greater security.

      "},{"location":"termux/neovim/","title":"Install neovim","text":"

      Neovim version 8 availabe as current package

      pkg install neovim\n
      "},{"location":"termux/neovim/#neovim-treesitter","title":"Neovim treesitter","text":"

      Treesitter provides excellent language syntax parsing and highlighting and is a very attractive feature of the recent neovim releases. Treesitter is a major attraction, bringing in a new audience for Neovim.

      The nvim-treesitter package is included in the practicalli/neovim-config-redux configuration.

      "},{"location":"termux/neovim/#c-compiler","title":"C Compiler","text":"

      Install C compiler for neovim-treesitter, to compile a parser for each specific programming language.

      pkg install clang\n

      gcc is not packaged for Termux, although there are guides to install gcc if preferred. clang has proved to be capable of creating the parsers used in the Practicalli configuration.

      "},{"location":"termux/neovim/#searching-files","title":"Searching files","text":"

      Telescope and other packages that involve searching for files recommend using ripgrep, a highly optomised tool for finding files on the operating system.

      pkg install ripgrep\n
      "},{"location":"termux/neovim/#optional-nodejs","title":"[optional] nodejs","text":"

      Optional. Only if node.js is required as a Neovim provider and JavaScript or ClojureScript development is to be done.

      pkg install nodejs\n
      "},{"location":"termux/setup/","title":"Termux Setup","text":"

      Launch Termux via its application icon. A black terminal screen will appear with a bash shell prompt.

      "},{"location":"termux/setup/#update-packages","title":"Update packages","text":"

      Check for new packages and update them all

      pkg upgrade -y\n

      If you wish to first check the packages that will be updated, use pkg --list-upgradable

      Select a specific region to minimise the number of mirrors checked during package upgrades, especially useful if on a limited data plan.

      termux-change-repo\n

      At time of writing, the Termux package on F-Droid was around 6 months old so there will be a number of packages that should be updated before any further installation steps are undertaken.

      "},{"location":"termux/setup/#configure-freedesktoporg-xdg-locations","title":"Configure Freedesktop.org XDG locations","text":"

      Edit the ~/.profile file, adding export directives to set the XDG locations:

      nano ~/.profile\n
      XDG locations
      # Common Free desktop.org locations\nexport XDG_CONFIG_HOME=$HOME/.config\nexport XDG_DATA_HOME=$HOME/.local/share\nexport XDG_STATE_HOME=$HOME/.local/state\nexport XDG_CACHE_HOME=$HOME/.cache\n\n# Set XDG location of Emacs Spacemacs configuration\nexport SPACEMACSDIR=\"$XDG_CONFIG_HOME/spacemacs\"\n

      Load the environment variables into the shell, or exit Termux and restart.

      Load .profile into shell
      source ~/.profile\n

      nano editor installed by default

      nano editor is used to edit the commands as the package is installed by default in termux. vim, neovim, emacs or any other Linux command line editor can be used if the package is first installed. Termux will list packages to install when trying to run a command that is from a package not yet installed.

      "},{"location":"termux/setup/#tools-to-download-binaries-and-configuration","title":"Tools to download binaries and configuration","text":"

      Many tools can be installed via the pkg tool, although specific Clojure tools and configuration require additional tools:

      • wget and curl - download tools not packaged, i.e. clojure-lsp binary
      • git - clone configuration files and projects (see Git version control section)
      • openssh - SSH service and tools to generate SSH keys
      pkg install curl wget git openssh\n

      Configure a Git Identify and SSH key to before committing and pushing changes, or cloning repositories using the SSH protocol. practicalli/dotfiles contains example configuration, ignore patterns and commit template for using Git.

      "},{"location":"termux/setup/#optional-configure-termux-settings","title":"[Optional] Configure Termux Settings","text":"

      Edit ~/.termux/termux.properties to configure the default settings for termux.

      nano ~/.termux/termux.properties\n

      Load termux.properties if values are changed (restarting Termux is not enough to load setting changes)

      termux-reload-settings\n

      The defaults are suitable for the majority of uses, although you may wish to consider:

      • fullscreen set to true to us the whole screen, hiding Android menu bars, etc.
      • hide-soft-keyboard-on-startup set to true if always using a physical keyboard
      • default-working-directory to save files user files and directories in an alternative location,

      If swiping from left edge of the screen is already taken, set key bindings for creating a new termux session, naming a session and switching between sessions. Alternatively, use byobu to create and switch between its tabs for multiple terminal sessions.

      "},{"location":"termux/setup/#set-color-scheme-and-font","title":"Set Color Scheme and Font","text":"

      The Termux:Styling plug provides menus for selecting terminal color scheme and font to use

      Press and hold on the Termux screen to show the context menu and select the Style menu. On smaller screens select More > Style

      If Termux:Styling plugin was not installed, a prompt will display asking if the plugin should be installed

      A menu appears with Choose Color and Choose Font

      Select Choose Color to select from the available list of colour schemes, e.g. Gruvbox Dark or Gruvbox Light

      ~/.termux/colors.properties file is added when selecting a colour scheme, defining Hex colors from the theme selected.

      Select Choose Font to select from the available fonts, e.g. FiraCode or Ubuntu

      ~/.termux/font.ttf font file is added when selecting a font.

      Termux:Styling uses NerdFonts for icons

      All fonts installed via Termux:Styling have been patched with NerdFonts, providing several thousand icons to use within the terminal prompt and Neovim itself (e.g. VimDevIcons).

      "},{"location":"termux/using-termux/","title":"Using Termux","text":"

      Start Termux app and a terminal prompt is shown, along with the standard Android software keyboard. An extended keyboard is provided with common key bindings for the command line interface (Tab, Esc, Ctrl, arrow keys, etc.).

      "},{"location":"termux/using-termux/#keyboards","title":"Keyboards","text":"

      Termux provides an extended keyboard with key combinations not possible with the Android software keyboard, i.e Ctrl-c, arrow keys, etc. TAB is especially useful for driving command and filename completion.

      Volume Up + q toggles the extended keyboard, so more screen is available when using a hardware keyboard.

      Connect a hardware keyboard for the best experience, e.g the Keyboard.io atreus is an excellent and highly portable mechanical keyboard. The software keyboard is automatically switched off when a hardware keyboard is connected, although the extended keyboard is still displayed by default.

      "},{"location":"termux/using-termux/#adjusting-font-size","title":"Adjusting Font size","text":"

      Pinch the screen inwards to zoom out making the text font smaller.

      Pinch the screen outwards to zoom in making the text font larger.

      "},{"location":"termux/using-termux/#termux-menus","title":"Termux menus","text":"

      Termux has three menus: A context menu, navigation drawer and Termux section of the Android notification.

      The context menu is shown by a long press anywhere on the terminal screen:

      • Select and Paste text to share text with other applications
      • Reset the terminal if it gets stuck or Hangup to exit the current terminal session
      • Style the terminal by selecting a font and a color scheme

      The navigation drawer is shown by swiping inwards from the left of the screen

      • list and select terminal sessions, set a name for a session with a long press
      • A button to toggle visibility of the touch keyboard.
      • A button to create new terminal sessions (long press for creating a named session or a fail-safe one).

      If gesture navigation is enabled in Android, hold the edge of the screen briefly before swiping to bring up the navigation drawer

      The Android notification menu contains a Termux section. Press the Termux section to show the current terminal session or expand the Termux section to exiting all running terminal sessions or aquire a wake lock to avoid Termux entering sleep mode. A wake lock allows server and other background processes to run reliably and to continue to receive notifications

      "},{"location":"termux/using-termux/#package-management","title":"Package management","text":"

      Termux provides a Linux command line experience, providing a wide range of Unix tools and development environments. Termux uses a Debian based system and packages are easily installed

      • apt install add tools and libraries to the Linux environment from the curated packages in the software center
      • apt update updates the list of packages fromhe software center
      • apt list --upgradable shows list of packages with new versions
      • apt upgrade install new versions of currently installed packages
      • apt-cache search --names-only - search for packages that include a specific pattern in their name.
      • apt-cache show - shows detail of the supplied package name, including a description

      pkg is an alias for apt, the advance package tool, although there seems little benefit to using pkg if familiar with apt (they are both 3 characters)

      "},{"location":"termux/using-termux/#byobu-terminal-tab-manager","title":"Byobu terminal tab manager","text":"

      Byobu is an alternative to Termux provides a single terminal prompt. Byobu provides multiple shell prompts, allowing individual Clojure tools and editors to be run from the Termux prompt simultaneously. Practicalli uses byobu to run Neovim, a Clojure REPL and unit test watcher in separate byobu tabs with the ability to add further tabs for other command line tools.

      pkg install byobu\n
      • F2 to create a new tab
      • F3 to select previous tab
      • F4 to select next tab

      byobu-enable command will configure the current shell to run byobu on startup. Test this is working by typing exit in Termux and start Termux app again. byobu-disable stops this behaviour and byobu will need to be run manually after starting Termux.

      Run the byobu-enable command again if zsh is configured after this step or if adding any other shell to Termux.

      "},{"location":"version-control/","title":"Version Control","text":"

      There are several ways to interact with Git version control, although Practicalli recommends Neogit interactive git client and Octo to manage GitHub issues and pull requests

      • lazygit terminal UI, embedded in Neovim (AstroNvim only)
      • Neogit git client similar to Emacs Magit, with Diffview integration
      • Octo for GitHub Issue and Pull Requests
      • Open in GitHub
      • Shell out to the command line, :!
      • Git commands in Neovim terminal buffer
      "},{"location":"version-control/#init-local-repository","title":"Init local repository","text":"AstroNvim

      Space t f opens floating terminal window in the current project directory root (or which ever directory Neovim was started from).

      Initialise a local git repository in the current directory.

      git init .\n

      "},{"location":"version-control/#stage-in-buffer","title":"Stage in buffer","text":"AstroNvimPracticalli Neovim Config Redux

      The current hunk or the whole buffer can be staged from the buffer using Git Signs, saving a trip to the Git Status buffer.

      Space g H stages the current hunk

      Space g S stages the current buffer

      Not supported.

      "},{"location":"version-control/#git-status","title":"Git Status","text":"AstroNvimPracticalli Neovim Config Redux

      SPC g g opens lazygit status, for minimal UI

      Space g s Space g n ++\"t\" opens neogit in a new tab for Magit style experience

      SPC g s opens Git Status tab, by running :Neogit

      "},{"location":"version-control/#github-integration","title":"GitHub integration","text":"

      Interact with the remote GitHub repository using Octo

      List issues from a specific repository

      :Octo issue list practicalli/neovim\n

      Create a pull request on a specific repository

      :Octo pr create practicalli/neovim\n
      "},{"location":"version-control/diff/","title":"Diff","text":"

      Compare differences between different files or between a file and its versions.

      :diffsplit filename Neovim command opens a split containing the selected filename, showing a diff comparision to the currently opened file

      file path completion helps select the correct file for comparison

      "},{"location":"version-control/diff/#git-diff","title":"Git Diff","text":"

      DiffView compares working space and staged changes side by side, or a diff for git merge conflicts.

      ++SPC++ g d or d in neogit status buffer (SPC g s) will open diffview in a new tab

      q to return to neogit status buffer

      • Green - added lines
      • Yellow - changed line
      • Red - deleted lines
      AstroNvimPracticalli Neovim Config

      Ctrl h / j / k / l to navigate between open splits

      SPC b toggles the sidebar buffer

      SPC w l and SPC w h to move cursor between diff buffer and sidebar buffer

      "},{"location":"version-control/lazygit/","title":"Lazygit version control","text":"Command Line or AstroNvim configuration

      Lazygit interface not provided by Practicalli Neovim Config Redux

      "},{"location":"version-control/lazygit/#requirements","title":"Requirements","text":"

      Install lazygit command line tool

      "},{"location":"version-control/lazygit/#open-lazygit","title":"Open Lazygit","text":"AstroNvimCommand Line

      SPC g g to open git status with lazygit in a popup window

      Change to the root directory of the git managed project.

      Run the lazygit rich terminal UI

      lazygit\n
      "},{"location":"version-control/lazygit/#use-lazygit","title":"Use Lazygit","text":"

      SPC to stage files or directories in the files section of the UI

      c for a simple commit message prompt in the lazygit UI

      C to create a commit message within the

      Define Editor for Git Commit Messages

      Set core.editor in the user Git configuration (i.e. .config/git/config) to the name of the editor to use for commit messages, e.g. nvim, emacsclient) shell title= git config --global core.editor = nvim Alternatively, use the VISUAL or EDITOR environment variable to the choice of editor

      "},{"location":"version-control/neogit/","title":"Neogit - interactive client for Git","text":"

      Neogit is an interactive git client that provides the core features of version control with Git. Neogit emulates many of the features found in magit.

      SPC g s to open :Neogit status buffer

      TAB toggles expansion of sections, files and hunks

      d provide a side-by-side view of changes

      q to quit Neogit and return to the previous tab

      Neovim is configured to use the magit style key bindings in practicalli/neovim-config-redux

      "},{"location":"version-control/neogit/#branching","title":"Branching","text":"

      b opens the branch menu,

      • b - checkout a branch
      • c - create a new branch
      • d - delete a branch, D deletes local and remote branch
      • l - checkout a remote branch and create a local tracking branch
      • m - rename an existing local branch
      • n - create a new branch
      "},{"location":"version-control/neogit/#staging-changes","title":"Staging changes","text":"

      s to stage change under cursor, either file or hunk. S to stage all changes

      u to unstage change under cursor, U to unstage all changes

      v to select lines to stage within a hunk using s or unstage with u

      "},{"location":"version-control/neogit/#commit","title":"Commit","text":"

      c for the commit menu

      c for a new commit, a to amend the latest commit, w to reword a commit message, e to add staged changes to existing commit

      A new commit or amend commit qill open a new window to write a commit message (using a git commit message template if defined)

      :wq to save a commit message and initiate the commit.

      :q! to cancel the commit from the commit message buffer.

      "},{"location":"version-control/neogit/#stashing-changes","title":"Stashing changes","text":"

      Z to open the stash menu

      z to stash the working copy and staged files (index)

      i to only stash the staged files (index)

      "},{"location":"version-control/neogit/#remote-changes","title":"Remote changes","text":"

      F to open the pull menu, p to pull changes (fetch and merge) from the remote repository, u t pull from the upstream repository, or e to specify the remote and branch names.

      P to open the push menu to open, -u to push to the current remote

      Confused when remote is not origin

      Use e option to push to elsewhere when the remote name is not set to origin. The e option will prompt for a remote name and branch.

      "},{"location":"version-control/neogit/#commit-history","title":"Commit history","text":"

      L l to view git commit history log

      RET on a log entry shows the commit details in a new window (split)

      q to close the commit details window

      "},{"location":"version-control/neogit/#modify-git-commit-history","title":"Modify Git commit history","text":"

      r opens the rebase menu

      "},{"location":"version-control/octo/","title":"Octo - GitHub issues and PRs","text":"

      List, create and edit issues and pull requests from Neovim with Octo package.

      Octo connects to GitHub via the GitHub CLI, using a developer token for authentication

      Neogit provides a Magit style client, creating commits, pull & push changes with remote repositories.

      "},{"location":"version-control/octo/#github-interaction","title":"GitHub interaction","text":"

      GitHub CLI

      Work with GitHub issues and Pull Requests from the comfort of Neovim.

      GitHub CLI to authentication to a GitHub account. Successful login creates a local developer token that is used by Octo to communicate with GitHub.

      gh auth login\n
      "},{"location":"version-control/octo/#octo-commands","title":"Octo commands","text":"

      Command line form: Octo <object> <action> [arguments] - Object, Action and Arguments commands

      List issues from current project (optionally add a specific repository)

      :Octo issue list practicalli/neovim\n

      The account/repository-name is required if Octo cannot find the repository

      Create a pull requests from current project

      :Octo pr create\n

      Add a comment to the current topic (issue/pr)

      :Octo comment add\n

      :Octo gist list\n

      Octo.nvim configuration options

      Octo.nvim configuration options

      "},{"location":"version-control/open-in-github/","title":"Open In GitHub","text":"

      Open a file under local version control in the GitHub web UI (browser window).

      Neovim

      :OpenInGHFile

      :OpenInGHRepo

      "}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index dc73a742..d3704b79 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -251,7 +251,12 @@ daily - https://practical.li/neovim/repl-driven-development/language-server-protocol/ + https://practical.li/neovim/repl-driven-development/refactor-tools/ + 2023-10-17 + daily + + + https://practical.li/neovim/repl-driven-development/structural-editing/ 2023-10-17 daily diff --git a/sitemap.xml.gz b/sitemap.xml.gz index a81351638d839d3cee98a4306eb5252c58e28608..e5b0ab9d22aa16b98f4683b7c9169f52fc18dc0d 100644 GIT binary patch literal 797 zcmV+&1LFK2iwFoDt}bN)|8r?{Wo=<_E_iKh0M(kycG@ryK==6y4trseF8Vlx?D_@m z2MB3wrpS__2M+oAj({Yz35#xeX4u#kgM1l}=ArTG;c0ZUJ*14m-_LIs%lQnv2^Rfu zKmYmTyZSPJSl_H_av92ugnhoBZ%c{ax^9}LMaBZ7j(HK%&}Flwi`<7X_`qY;7FTo?Evg0)8nYE%0jlA6%l zFsY(963EBu3M#t4b_y)=b-5p4M^d_}DTgG&NPH;RlSFeEB7mc-19~-TPdRRqK%7A* zvsEp6XagdMJ;n$!yc`uaBYLh|X@9algKu7qsSPBcNCBQqlnP(vXx>Uoj5{mms^=Z$ zi&9(RvquYDW5hdfsxZCP$*QQ!mOFD>ak(5E$N@Dv3!)tqCBan=?+y-XLm6-@hTgsx zgFgnw+1bay`=)B0LiJV-w$~|ziEXj*5ps^8cTLv*R((jy z!X8|R0s{HvAIOyo>kJM|k%RrrI_N~FY{ESy_-B)7+eETpP4Il19M_=9Qb?FqBI7qd zuCeEmswp*b-{rc-Um){^Zudp(P-@_+hBZ*`CxkRo b_Jm%MYPa=2bYYJNa*gZXo3A;4L@odTd*p+Y literal 792 zcmV+z1Lyo7iwFpJaV}*7|8r?{Wo=<_E_iKh0M(kycH%G$K==I$4to>YSRU45zdVw>tV6FdmP2M6+xhikIiG!xX1B+M}CkQNk&E}1P~r0zV}Ef?SC>#G@m z>IA2=<7ef_tm`a^rwLJ;j7GbK!_EWkaqL!ImAL$YPI)A|zFFRUQP)d#{gp+|V;5p( ztNj3dgom}&=nkvyV&p{9y`cOnln$sekxPx@C4NPz!%+{w?|=+y*CweU-{VlC&TMrO z5c%eje6xo68SH6XSx7jD(DRD~3kbs%t?Qu84x&Wrqpy?7?TU8jK`eiDGAc)4o6osb z`$3;FW48@w9Nm`)Z9}%4o1y2RXNx%+tD$i$9`iwkNcUiJ5&_LuxG9-aZvnVH=*$+I zM4);PP5@|x;M7EM*E^a7@J2Wg99$XDt5JK-aiav{1Uj0n zs?kFm5JBuQ1d!q7xUd<~x4M<~H|rDl>eU*xfe0Kaz_W>R;j0|aTX>1J&f0R-^N#Xa zskQLgqlH#u#5-`VFum2$s-W|>b>_O_Qa(7418Q^f{Ps99URn#GvHbby?!qS zf24-9vyXxIRnKz{iEsc>PPz=6qfu%DU^I>9L$anA|<*(B;Vkt|q4zTGCrXV5Phb%MBuSQG0^ zhMcUY$dKCIew%CH4eRnwAlADNS%H - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/termux/custom-shell/index.html b/termux/custom-shell/index.html index 8618fa13..32d6dcb7 100644 --- a/termux/custom-shell/index.html +++ b/termux/custom-shell/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/termux/fdroid-install/index.html b/termux/fdroid-install/index.html index 0fab4683..a9ab36ad 100644 --- a/termux/fdroid-install/index.html +++ b/termux/fdroid-install/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/termux/git/index.html b/termux/git/index.html index 900e12cd..905624f2 100644 --- a/termux/git/index.html +++ b/termux/git/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/termux/index.html b/termux/index.html index eff0cba0..d3560dbd 100644 --- a/termux/index.html +++ b/termux/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/termux/neovim/index.html b/termux/neovim/index.html index b38ae23b..286526f1 100644 --- a/termux/neovim/index.html +++ b/termux/neovim/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/termux/setup/index.html b/termux/setup/index.html index 76575de5..3352e4b2 100644 --- a/termux/setup/index.html +++ b/termux/setup/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/termux/using-termux/index.html b/termux/using-termux/index.html index 45ecb984..fde1e399 100644 --- a/termux/using-termux/index.html +++ b/termux/using-termux/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/version-control/diff/index.html b/version-control/diff/index.html index d38d9e40..053051c3 100644 --- a/version-control/diff/index.html +++ b/version-control/diff/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/version-control/index.html b/version-control/index.html index 4a4a01a9..81b6858a 100644 --- a/version-control/index.html +++ b/version-control/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/version-control/lazygit/index.html b/version-control/lazygit/index.html index 0c38fd9e..473bb134 100644 --- a/version-control/lazygit/index.html +++ b/version-control/lazygit/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/version-control/neogit/index.html b/version-control/neogit/index.html index e860eecb..ca9b5d26 100644 --- a/version-control/neogit/index.html +++ b/version-control/neogit/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/version-control/octo/index.html b/version-control/octo/index.html index 45db7216..37afa76d 100644 --- a/version-control/octo/index.html +++ b/version-control/octo/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools diff --git a/version-control/open-in-github/index.html b/version-control/open-in-github/index.html index 4d918ffd..3c17f847 100644 --- a/version-control/open-in-github/index.html +++ b/version-control/open-in-github/index.html @@ -1326,6 +1326,8 @@ + + @@ -1395,11 +1397,31 @@
    • - + + + + + Structural Editing + + + + +
    • + + + + + + + + + +
    • + - LSP + Refactor Tools