diff options
author | Thomas Voss <mail@thomasvoss.com> | 2024-02-23 15:19:26 +0100 |
---|---|---|
committer | Thomas Voss <mail@thomasvoss.com> | 2024-02-23 15:19:26 +0100 |
commit | 208ddaa76019af05d1e0f352912dab38ceedad6b (patch) | |
tree | eafd9aefa77d7cf9581c2e4de87968d700d74aaf /src/blog | |
parent | ab6d91982264623ae626115349d2c8a7579f1b1e (diff) |
Add nvim-ts article
Diffstat (limited to 'src/blog')
-rw-r--r-- | src/blog/index.gsp | 44 | ||||
-rw-r--r-- | src/blog/nvim-ts/change-command.diff | 9 | ||||
-rw-r--r-- | src/blog/nvim-ts/final.lua | 28 | ||||
-rw-r--r-- | src/blog/nvim-ts/final.webm | bin | 0 -> 207067 bytes | |||
-rw-r--r-- | src/blog/nvim-ts/get-cursor.diff | 12 | ||||
-rw-r--r-- | src/blog/nvim-ts/get-parent.diff | 14 | ||||
-rw-r--r-- | src/blog/nvim-ts/git-log | 17 | ||||
-rw-r--r-- | src/blog/nvim-ts/git-log-2 | 11 | ||||
-rw-r--r-- | src/blog/nvim-ts/git-rebase | 34 | ||||
-rw-r--r-- | src/blog/nvim-ts/git-rebase-2 | 3 | ||||
-rw-r--r-- | src/blog/nvim-ts/index.gsp | 205 | ||||
-rw-r--r-- | src/blog/nvim-ts/skeleton.lua | 14 | ||||
-rw-r--r-- | src/blog/nvim-ts/ts-tree.scm | 12 |
13 files changed, 403 insertions, 0 deletions
diff --git a/src/blog/index.gsp b/src/blog/index.gsp new file mode 100644 index 0000000..4d4aab8 --- /dev/null +++ b/src/blog/index.gsp @@ -0,0 +1,44 @@ +html lang="en" { + head { HEAD } + body { + header { + div .head { + h1 {-Blog Posts} + INCLUDE(nav.gsp) + } + + figure .quote { + blockquote { + p {= + Object-oriented programming is an exceptionally bad idea which could + only have originated in California. + } + } + figcaption {-Edsgar W. Dijkstra} + } + } + + main { + p {= + On this section of the site you will find my blog. Most of what I post + here is related to software-development and -design, although maybe + you’ll find something unrelated on here too. + } + + p {-Posts:} + + ul { + m4_dnl ARTICLE(grab, {-Making Grep Better}) + m4_dnl ARTICLE(andy-val, {-Values in Andy}) + m4_dnl ARTICLE(new-sh, {-Making a New Shell}) + m4_dnl ARTICLE(extend, {-Extensible Scripting}) + m4_dnl ARTICLE(gsp, {-Writing an HTML Preprocessor (feat. Tree-Sitter)}) + m4_dnl ARTICLE(fw-ec, {-Patching My Laptop’s Embedded Controller}) + + ARTICLE(nvim-ts, {-Hacking with Tree-Sitter on Neovim}) + } + } + + footer { FOOT } + } +} diff --git a/src/blog/nvim-ts/change-command.diff b/src/blog/nvim-ts/change-command.diff new file mode 100644 index 0000000..43bcdf9 --- /dev/null +++ b/src/blog/nvim-ts/change-command.diff @@ -0,0 +1,9 @@ + end + + if node ~= nil then +- -- TODO ++ local sr, sc, er, ec = node:child(0):range() ++ vim.api.nvim_buf_set_text(0, sr, sc, er, ec, { rhs }) + end + end, { + buffer = true, diff --git a/src/blog/nvim-ts/final.lua b/src/blog/nvim-ts/final.lua new file mode 100644 index 0000000..948ee30 --- /dev/null +++ b/src/blog/nvim-ts/final.lua @@ -0,0 +1,28 @@ +local ts_utils = require('nvim-treesitter.ts_utils') + +local function map(lhs, rhs) + vim.keymap.set('n', lhs, function() + local node = ts_utils.get_node_at_cursor() + if node == nil then + error('No tree-sitter parser found.') + end + + while node ~= nil and node:type() ~= 'operation' do + node = node:parent() + end + + if node ~= nil then + local sr, sc, er, ec = node:child(0):range() + vim.api.nvim_buf_set_text(0, sr, sc, er, ec, { rhs }) + end + end, { + buffer = true, + noremap = true, + silent = true, + }) +end + +map('p', 'pick') +map('r', 'reword') +map('s', 'squash') +map('f', 'fixup') diff --git a/src/blog/nvim-ts/final.webm b/src/blog/nvim-ts/final.webm Binary files differnew file mode 100644 index 0000000..cbe2c55 --- /dev/null +++ b/src/blog/nvim-ts/final.webm diff --git a/src/blog/nvim-ts/get-cursor.diff b/src/blog/nvim-ts/get-cursor.diff new file mode 100644 index 0000000..3e34afc --- /dev/null +++ b/src/blog/nvim-ts/get-cursor.diff @@ -0,0 +1,12 @@ ++local ts_utils = require('nvim-treesitter.ts_utils') ++ + local function map(lhs, rhs) + vim.keymap.set('n', lhs, function() +- -- TODO ++ local node = ts_utils.get_node_at_cursor() ++ if node == nil then ++ error('No Tree-Sitter parser found.') ++ end + end, { + buffer = true, + noremap = true, diff --git a/src/blog/nvim-ts/get-parent.diff b/src/blog/nvim-ts/get-parent.diff new file mode 100644 index 0000000..9599792 --- /dev/null +++ b/src/blog/nvim-ts/get-parent.diff @@ -0,0 +1,14 @@ + if node == nil then + error('No Tree-Sitter parser found.') + end ++ ++ while node ~= nil and node:type() ~= 'operation' do ++ node = node:parent() ++ end ++ ++ if node ~= nil then ++ -- TODO ++ end + end, { + buffer = true, + noremap = true, diff --git a/src/blog/nvim-ts/git-log b/src/blog/nvim-ts/git-log new file mode 100644 index 0000000..d6f7870 --- /dev/null +++ b/src/blog/nvim-ts/git-log @@ -0,0 +1,17 @@ +commit 1893588f4f57024098a538d471caeecd01fbe88d +Author: Thomas Voss <mail@thomasvoss.com> +Date: Tue Nov 14 10:36:33 2023 +0100 + + Fix another typo + +commit b592402d702379d14415c1becf18f6f40c2d6d76 +Author: Thomas Voss <mail@thomasvoss.com> +Date: Tue Nov 14 10:12:05 2023 +0100 + + Add new blog post + +commit 6ae48a4d7a84fd4c76a08a110514754dd6949d46 +Author: Thomas Voss <mail@thomasvoss.com> +Date: Tue Nov 14 10:10:53 2023 +0100 + + Fix various typos diff --git a/src/blog/nvim-ts/git-log-2 b/src/blog/nvim-ts/git-log-2 new file mode 100644 index 0000000..0f34811 --- /dev/null +++ b/src/blog/nvim-ts/git-log-2 @@ -0,0 +1,11 @@ +commit 74ba5ba2b8bdb7708a25283f03542811706072bd +Author: Thomas Voss <mail@thomasvoss.com> +Date: Tue Nov 14 10:12:05 2023 +0100 + + Add new blog post + +commit a893a1752b1365ec6437095506e6e63979b9236d +Author: Thomas Voss <mail@thomasvoss.com> +Date: Tue Nov 14 10:10:53 2023 +0100 + + Fix various typos diff --git a/src/blog/nvim-ts/git-rebase b/src/blog/nvim-ts/git-rebase new file mode 100644 index 0000000..f8c3a91 --- /dev/null +++ b/src/blog/nvim-ts/git-rebase @@ -0,0 +1,34 @@ +pick d620266 Fix various typos +pick 59fa2b6 Add new blog post +pick 4c45214 Fix another typo + +# Rebase 10c3013..4c45214 onto 10c3013 (3 commands) +# +# Commands: +# p, pick <commit> = use commit +# r, reword <commit> = use commit, but edit the commit message +# e, edit <commit> = use commit, but stop for amending +# s, squash <commit> = use commit, but meld into previous commit +# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous +# commit's log message, unless -C is used, in which case +# keep only this commit's message; -c is same as -C but +# opens the editor +# x, exec <command> = run command (the rest of the line) using shell +# b, break = stop here (continue rebase later with 'git rebase --continue') +# d, drop <commit> = remove commit +# l, label <label> = label current HEAD with a name +# t, reset <label> = reset HEAD to a label +# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] +# create a merge commit using the original merge commit's +# message (or the oneline, if no original merge commit was +# specified); use -c <commit> to reword the commit message +# u, update-ref <ref> = track a placeholder for the <ref> to be updated +# to this position in the new commits. The <ref> is +# updated at the end of the rebase +# +# These lines can be re-ordered; they are executed from top to bottom. +# +# If you remove a line here THAT COMMIT WILL BE LOST. +# +# However, if you remove everything, the rebase will be aborted. +# diff --git a/src/blog/nvim-ts/git-rebase-2 b/src/blog/nvim-ts/git-rebase-2 new file mode 100644 index 0000000..781738e --- /dev/null +++ b/src/blog/nvim-ts/git-rebase-2 @@ -0,0 +1,3 @@ +pick d620266 Fix various typos +fixup 4c45214 Fix another typo +pick 59fa2b6 Add new blog post diff --git a/src/blog/nvim-ts/index.gsp b/src/blog/nvim-ts/index.gsp new file mode 100644 index 0000000..e394e49 --- /dev/null +++ b/src/blog/nvim-ts/index.gsp @@ -0,0 +1,205 @@ +html lang="en" { + head { HEAD } + body { + header { + div .head { + h1 {-Learn Your Tools} + INCLUDE(nav.gsp) + } + + figure .quote { + blockquote { + p {= + Vim is a component among other components. Don’t turn it into a + massive application, but have it work well together with other + programs. + } + } + figcaption {-Vim Reference Manual} + } + } + + main { + h2 #rebasing {-Git Rebasing} + p {= + I’m always working with Git. I use it at work, I use it for my personal + projects, I even use it for this site. Git has become a part of my + everyday life. One Git feature that I use quite often is Git-Rebase, + which when invoked with the @code{--i} flag allows you to reorder-, + combine-, and delete commits interactively. + } + + p {= + Let’s say you make two commits called ‘Fix various typos’ and ‘Add new + blog post’ in that order. Now imagine you found another typo that you + forgot to fix in your first commit. You might end up with a history + like so: + } + + figure { FMT_CODE(git-log) } + + p {= + While for many people this might be fine, I personally find it much more + clean to have the first and third commits merged into one commit, as + they’re two parts of the same task. This is where Git-Rebase comes in. + We can run @code{-git rebase -i HEAD~N} where @code{-N} is + the number of commits back we want to include, which in this case would + be 3. Running that command will open the following buffer in your text + editor. In my case, Neovim. + } + + figure { FMT_CODE(git-rebase) } + + p {= + As suggested by the comments added to the bottom of the opened buffer, + we can combine the two typo-fixing commits by simply swapping the 2nd- + and 3rd lines, and then changing the second typo-fixing commit from a + @em{-pick} to a @em{-fixup}: + } + + figure { FMT_CODE(git-rebase-2) } + + p {= + After saving and exiting from your editor, the Git log should now only + have two entries, which looks a lot cleaner. + } + + figure { FMT_CODE(git-log-2) } + + h2 #problem {-The Problem} + p {= + This is fine and all, but it could be better. Specifically, it would be + nice if instead of having to navigate to the front of the line, delete + the word, and replace it with something new (such as @em{-fixup}), you + could just hit ‘@kbd{-f}’ on your keyboard with your cursor on the right + line and have it edit the command for you. Along with fixups, it would + also be nice to be able to press ‘@kbd{-s}’ for @em{-squash}, ‘@kbd{-r}’ + for @em{-reword}, etc. + } + + p {= + Seeing as the Git-Rebase interface is line-based with a simple syntax, + you could probably easily do this with a regular-expression-based + solution. I’m going to use Tree-Sitter though because it’s cooler, and + I want to show off how easy it is to use. + } + + h2 #plugin {-Writing The Plugin} + p {= + The first thing to figure out is where to put the plugin. Seeing as we + only want it active when we’re performing a rebase, we can make use of + the @code{-after/ftplugin} directory. Configurations placed in this + directory will only be applied when working in a buffer whose filetype + corresponds to the filename. By running @code{-:set ft?} in a + Git-Rebase buffer we can see that the filetype is ‘@code{-gitrebase}’, + so with all that information we know that we can put our plugin in + @code{-after/ftplugin/gitrebase.lua}. + } + + p {= + The basic skeleton of the plugin is going to look like so: + } + + figure { + figcaption { + code {-after/ftplugin/gitrebase.lua} + } + FMT_CODE(skeleton.lua) + } + + p {= + The @code{-map} function defined here will create a normal-mode + keybinding where pressing the key combination provided as the first + argument will replace the Git-Rebase command of the current line with + the string provided in the second argument. The actual function to + perform this replacement isn’t implemented yet, so in its current state + it will bind these keys to an empty function. We also pass a few + options to @code{-vim.keymap.set}; you can read more about these in + @code{-:help vim.keymap.set} if you’re interested. + } + + p {= + The first step to implementing the actual functionality of the plugin is + to figure out where we are. We can do this very easily with the Neovim + Tree-Sitter API: + } + + figure { FMT_CODE(get-cursor.diff) } + + p {- + The @code{-ts_utils.get_node_at_cursor()} function will return to us the + current node in the Tree-Sitter parse tree that our cursor is located + at. In the case that we don’t have a Tree-Sitter parser available, the + node will be @code{-nil} and we can just issue an error. + } + + p {= + Before making any more progress, it’s a good idea to make sure you have + a proper understanding of the structure of the @code{-gitrebase} AST. + You can view the AST by opening a new @code{-gitrebase} buffer and + running @code{-:vim.treesitter.inspect_tree()}. I implore you to do + this yourself, you’ll learn from it. The AST will end up looking + something like this, followed by a bunch of @code{-(comment)}s: + } + + figure { FMT_CODE(ts-tree.scm) } + + p {= + As you can see, each line is represented by an @em{-operation} node + which has three child nodes: a @em{-command}, a @em{-label}, and a + @em{-message}. You can probably begin to realize now that we’re going + to want to get- and modify the @em{-command} node on the line that our + cursor is on. + } + + p {= + In the code above we got the node at our cursor, now we need to traverse + the AST to the operation node. We can call the @code{-:parent()} method + on our node in a loop to traverse up the tree until we reach our target + node. If our cursor isn’t on a valid line such as on a comment or a + blank line we won’t ever hit an operation node and will instead get + @code{-nil}, so we need to handle that case too. + } + + figure { FMT_CODE(get-parent.diff) } + + p {= + Now that we have the operation node, we simply have to get the child + command node (which we know is the first child node), find out where in + the buffer it is, and replace it. We can call the @code{-:child(0)} + method on our node to get the first child, and then call the + @code{-:range()} method on the child to get position in our buffer of + the command node. The @code{-:range()} method returns 4 values: the + start row, start column, end row, and end column. We can then pass + these positions to @code{-vim.api.nvim_buf_set_text()} to set the text + at the given position. + } + + figure { FMT_CODE(change-command.diff) } + + p {- + And that is the entire plugin! In just 28 lines of code (including + whitespace) we implemented a plugin using Tree-Sitter to allow you to + modify a Git-Rebase command with a single keystroke. The completed + product looks like so: + } + + figure { + figcaption { + code {-after/ftplugin/gitrebase.lua} + } + FMT_CODE(final.lua) + } + + figure { + figcaption {-Example Usage} + video width="100%" height="720" controls {= + @source src="final.webm" type="video/webm" {} + Your browser does not support the video tag. + } + } + } + + footer { FOOT } + } +} diff --git a/src/blog/nvim-ts/skeleton.lua b/src/blog/nvim-ts/skeleton.lua new file mode 100644 index 0000000..0f9a54d --- /dev/null +++ b/src/blog/nvim-ts/skeleton.lua @@ -0,0 +1,14 @@ +local function map(lhs, rhs) + vim.keymap.set('n', lhs, function() + --- TODO + end, { + buffer = true, + noremap = true, + silent = true, + }) +end + +map('p', 'pick') +map('r', 'reword') +map('s', 'squash') +map('f', 'fixup') diff --git a/src/blog/nvim-ts/ts-tree.scm b/src/blog/nvim-ts/ts-tree.scm new file mode 100644 index 0000000..e255ade --- /dev/null +++ b/src/blog/nvim-ts/ts-tree.scm @@ -0,0 +1,12 @@ +(operation) ; [1:1 - 30] + (command) ; [1:1 - 4] + (label) ; [1:6 - 12] + (message) ; [1:14 - 30] +(operation) ; [2:1 - 30] + (command) ; [2:1 - 4] + (label) ; [2:6 - 12] + (message) ; [2:14 - 30] +(operation) ; [3:1 - 29] + (command) ; [3:1 - 4] + (label) ; [3:6 - 12] + (message) ; [3:14 - 29] |