summaryrefslogtreecommitdiffhomepage
path: root/src/blog/nvim-ts/index.gsp
diff options
context:
space:
mode:
Diffstat (limited to 'src/blog/nvim-ts/index.gsp')
-rw-r--r--src/blog/nvim-ts/index.gsp205
1 files changed, 205 insertions, 0 deletions
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 }
+ }
+}