From ea77956c0224d9f9997d60e91642a4e255750f5b Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Tue, 14 Nov 2023 18:10:22 +0100 Subject: Add a new blog post --- src/blog/nvim-ts/index.gsp | 226 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 src/blog/nvim-ts/index.gsp (limited to 'src/blog/nvim-ts/index.gsp') diff --git a/src/blog/nvim-ts/index.gsp b/src/blog/nvim-ts/index.gsp new file mode 100644 index 0000000..c0bfdf6 --- /dev/null +++ b/src/blog/nvim-ts/index.gsp @@ -0,0 +1,226 @@ +html lang="en" { + head { m4_include(head.gsp) } + body { + header { + div { + h1 {-Learn Your Tools} + m4_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. On 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 { + pre {= m4_fmt_code(git-log.gsp) } + } + + 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 { + pre {= m4_fmt_code(git-rebase.gsp) } + } + + 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 { + pre {= m4_fmt_code(git-rebase-2.gsp) } + } + + p {- + After saving and exiting from your editor, the Git log should now only + have two entries, which looks a lot cleaner. + } + + figure { + pre {= m4_fmt_code(git-log-2.gsp) } + } + + 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} + } + pre {= m4_fmt_code(skeleton.lua.gsp) } + } + + 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 it’s 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 m4_abbr(API): + } + + figure { + pre {= m4_fmt_code(get-cursor.diff.gsp) } + } + + 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} + m4_abbr(AST). You can view the m4_abbr(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 m4_abbr(AST) will end up looking + something like this, followed by a bunch of @code{-(comment)}s: + } + + figure { + pre {= m4_fmt_code(ts-tree.scm.gsp) } + } + + 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 m4_abbr(AST) to the operation node. We can call the @em{-: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 { + pre {= m4_fmt_code(get-parent.diff.gsp) } + } + + 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 { + pre {= m4_fmt_code(change-command.diff.gsp) } + } + + p {- + And that is the entire plugin! In just 24 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} + } + pre {= m4_fmt_code(final.lua.gsp) } + } + + 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. + } + } + } + + hr{} + + footer { m4_footer } + } +} -- cgit v1.2.3