summaryrefslogtreecommitdiffhomepage
path: root/src/blog
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2023-11-14 18:10:22 +0100
committerThomas Voss <mail@thomasvoss.com> 2023-11-14 18:10:22 +0100
commitea77956c0224d9f9997d60e91642a4e255750f5b (patch)
tree7d47222427808b35f9fe07fb5e3249d08fadf109 /src/blog
parentd31314638a4499ce36def937b61d2069f496eaaa (diff)
Add a new blog post
Diffstat (limited to 'src/blog')
-rw-r--r--src/blog/index.gsp1
-rw-r--r--src/blog/nvim-ts/change-command.diff.gsp9
-rw-r--r--src/blog/nvim-ts/final.lua.gsp28
-rw-r--r--src/blog/nvim-ts/final.webmbin0 -> 207067 bytes
-rw-r--r--src/blog/nvim-ts/get-cursor.diff.gsp12
-rw-r--r--src/blog/nvim-ts/get-parent.diff.gsp14
-rw-r--r--src/blog/nvim-ts/git-log-2.gsp11
-rw-r--r--src/blog/nvim-ts/git-log.gsp17
-rw-r--r--src/blog/nvim-ts/git-rebase-2.gsp3
-rw-r--r--src/blog/nvim-ts/git-rebase.gsp34
-rw-r--r--src/blog/nvim-ts/index.gsp226
-rw-r--r--src/blog/nvim-ts/skeleton.lua.gsp14
-rw-r--r--src/blog/nvim-ts/ts-tree.scm.gsp12
13 files changed, 381 insertions, 0 deletions
diff --git a/src/blog/index.gsp b/src/blog/index.gsp
index 68b53f8..030ad3e 100644
--- a/src/blog/index.gsp
+++ b/src/blog/index.gsp
@@ -28,6 +28,7 @@ html lang="en" {
p {-Posts:}
ul {
+ m4_article(nvim-ts, {-Hacking with Tree-Sitter on Neovim})
m4_article(gsp, {-Writing an HTML Preprocessor (feat. Tree-Sitter)})
m4_article(fw-ec, {-Patching My Laptop’s Embedded Controller})
}
diff --git a/src/blog/nvim-ts/change-command.diff.gsp b/src/blog/nvim-ts/change-command.diff.gsp
new file mode 100644
index 0000000..8f1aeeb
--- /dev/null
+++ b/src/blog/nvim-ts/change-command.diff.gsp
@@ -0,0 +1,9 @@
+ end
+
+ if node ~= nil then
+@span .diff-del {-- -- TODO}
+@span .diff-ins {-+ local sr, sc, er, ec = node:child(0):range()}
+@span .diff-ins {-+ 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.gsp b/src/blog/nvim-ts/final.lua.gsp
new file mode 100644
index 0000000..08ef220
--- /dev/null
+++ b/src/blog/nvim-ts/final.lua.gsp
@@ -0,0 +1,28 @@
+@span .kw {-local} ts_utils @span .op {-=} @span .fn {-require}(@span .str {-'nvim-treesitter.ts_utils'})
+
+@span .kw {-local} @span .kw {-function} @span .fn {-map}(lhs, rhs)
+ vim.keymap.@span .fn {-set}(@span .str {-'n'}, lhs, @span .kw {-function}()
+ @span .kw {-local} node @span .op {-=} ts_utils.@span .fn {-get_node_at_cursor}()
+ @span .kw {-if} node @span .op {-==} @span .cnst {-nil} @span .kw {-then}
+ @span .fn {-error}(@span .str {-'No tree-sitter parser found.'})
+ @span .kw {-end}
+
+ @span .kw {-while} node @span .op {-~=} @span .cnst {-nil} @span .op {-and} node:@span .fn {-type}() @span .op {-~=} @span .str {-'operation'} @span .kw {-do}
+ node @span .op {-=} node:@span .fn {-parent}()
+ @span .kw {-end}
+
+ @span .kw {-if} node @span .op {-~=} @span .cnst {-nil} @span .kw {-then}
+ @span .kw {-local} sr, sc, er, ec @span .op {-=} node:@span .fn {-child}(0):@span .fn {-range}()
+ vim.api.@span .fn {-nvim_buf_set_text}(@span .str {-0}, sr, sc, er, ec, { rhs \})
+ @span .kw {-end}
+ @span .kw {-end}, {
+ buffer @span .op {-=} @span .cnst {-true},
+ noremap @span .op {-=} @span .cnst {-true},
+ silent @span .op {-=} @span .cnst {-true},
+ \})
+@span .kw {-end}
+
+@span .fn {-map}(@span .str {-'p'}, @span .str {-'pick'})
+@span .fn {-map}(@span .str {-'r'}, @span .str {-'reword'})
+@span .fn {-map}(@span .str {-'s'}, @span .str {-'squash'})
+@span .fn {-map}(@span .str {-'f'}, @span .str {-'fixup'})
diff --git a/src/blog/nvim-ts/final.webm b/src/blog/nvim-ts/final.webm
new file mode 100644
index 0000000..cbe2c55
--- /dev/null
+++ b/src/blog/nvim-ts/final.webm
Binary files differ
diff --git a/src/blog/nvim-ts/get-cursor.diff.gsp b/src/blog/nvim-ts/get-cursor.diff.gsp
new file mode 100644
index 0000000..6e8f759
--- /dev/null
+++ b/src/blog/nvim-ts/get-cursor.diff.gsp
@@ -0,0 +1,12 @@
+@span .diff-ins {-+local ts_utils = require('nvim-treesitter.ts_utils')}
+@span .diff-ins {-+}
+ local function map(lhs, rhs)
+ vim.keymap.set('n', lhs, function()
+@span .diff-del {-- -- TODO}
+@span .diff-ins {-+ local node = ts_utils.get_node_at_cursor()}
+@span .diff-ins {-+ if node == nil then}
+@span .diff-ins {-+ error('No Tree-Sitter parser found.')}
+@span .diff-ins {-+ end}
+ end, {
+ buffer = true,
+ noremap = true,
diff --git a/src/blog/nvim-ts/get-parent.diff.gsp b/src/blog/nvim-ts/get-parent.diff.gsp
new file mode 100644
index 0000000..eebfeb5
--- /dev/null
+++ b/src/blog/nvim-ts/get-parent.diff.gsp
@@ -0,0 +1,14 @@
+ if node == nil then
+ error('No Tree-Sitter parser found.')
+ end
+@span .diff-ins {-+}
+@span .diff-ins {-+ while node ~= nil and node:type() ~= 'operation' do}
+@span .diff-ins {-+ node = node:parent()}
+@span .diff-ins {-+ end}
+@span .diff-ins {-+}
+@span .diff-ins {-+ if node ~= nil then}
+@span .diff-ins {-+ -- TODO}
+@span .diff-ins {-+ end}
+ end, {
+ buffer = true,
+ noremap = true,
diff --git a/src/blog/nvim-ts/git-log-2.gsp b/src/blog/nvim-ts/git-log-2.gsp
new file mode 100644
index 0000000..fbab82e
--- /dev/null
+++ b/src/blog/nvim-ts/git-log-2.gsp
@@ -0,0 +1,11 @@
+@span .fn {-commit 74ba5ba2b8bdb7708a25283f03542811706072bd}
+Author: Thomas Voss <mail\@thomasvoss.com>
+Date: Tue Nov 14 10:12:05 2023 +0100
+
+ Add new blog post
+
+@span .fn {-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-log.gsp b/src/blog/nvim-ts/git-log.gsp
new file mode 100644
index 0000000..2a7ae4d
--- /dev/null
+++ b/src/blog/nvim-ts/git-log.gsp
@@ -0,0 +1,17 @@
+@span .fn {-commit 1893588f4f57024098a538d471caeecd01fbe88d}
+Author: Thomas Voss <mail\@thomasvoss.com>
+Date: Tue Nov 14 10:36:33 2023 +0100
+
+ Fix another typo
+
+@span .fn {-commit b592402d702379d14415c1becf18f6f40c2d6d76}
+Author: Thomas Voss <mail\@thomasvoss.com>
+Date: Tue Nov 14 10:12:05 2023 +0100
+
+ Add new blog post
+
+@span .fn {-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-rebase-2.gsp b/src/blog/nvim-ts/git-rebase-2.gsp
new file mode 100644
index 0000000..6987bab
--- /dev/null
+++ b/src/blog/nvim-ts/git-rebase-2.gsp
@@ -0,0 +1,3 @@
+@span .fn {-pick} @span .var {-d620266} Fix various typos
+@span .fn {-fixup} @span .var {-4c45214} Fix another typo
+@span .fn {-pick} @span .var {-59fa2b6} Add new blog post
diff --git a/src/blog/nvim-ts/git-rebase.gsp b/src/blog/nvim-ts/git-rebase.gsp
new file mode 100644
index 0000000..d41262a
--- /dev/null
+++ b/src/blog/nvim-ts/git-rebase.gsp
@@ -0,0 +1,34 @@
+@span .fn {-pick} @span .var {-d620266} Fix various typos
+@span .fn {-pick} @span .var {-59fa2b6} Add new blog post
+@span .fn {-pick} @span .var {-4c45214} Fix another typo
+
+@span .cmt {-# Rebase 10c3013..4c45214 onto 10c3013 (3 commands)}
+@span .cmt {-#}
+@span .cmt {-# Commands:}
+@span .cmt {-# p, pick <commit> = use commit}
+@span .cmt {-# r, reword <commit> = use commit, but edit the commit message}
+@span .cmt {-# e, edit <commit> = use commit, but stop for amending}
+@span .cmt {-# s, squash <commit> = use commit, but meld into previous commit}
+@span .cmt {-# f, fixup [-C | -c] <commit> = like "squash" but keep only the previous}
+@span .cmt {-# commit's log message, unless -C is used, in which case}
+@span .cmt {-# keep only this commit's message; -c is same as -C but}
+@span .cmt {-# opens the editor}
+@span .cmt {-# x, exec <command> = run command (the rest of the line) using shell}
+@span .cmt {-# b, break = stop here (continue rebase later with 'git rebase --continue')}
+@span .cmt {-# d, drop <commit> = remove commit}
+@span .cmt {-# l, label <label> = label current HEAD with a name}
+@span .cmt {-# t, reset <label> = reset HEAD to a label}
+@span .cmt {-# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]}
+@span .cmt {-# create a merge commit using the original merge commit's}
+@span .cmt {-# message (or the oneline, if no original merge commit was}
+@span .cmt {-# specified); use -c <commit> to reword the commit message}
+@span .cmt {-# u, update-ref <ref> = track a placeholder for the <ref> to be updated}
+@span .cmt {-# to this position in the new commits. The <ref> is}
+@span .cmt {-# updated at the end of the rebase}
+@span .cmt {-#}
+@span .cmt {-# These lines can be re-ordered; they are executed from top to bottom.}
+@span .cmt {-#}
+@span .cmt {-# If you remove a line here THAT COMMIT WILL BE LOST.}
+@span .cmt {-#}
+@span .cmt {-# However, if you remove everything, the rebase will be aborted.}
+@span .cmt {-#}
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 }
+ }
+}
diff --git a/src/blog/nvim-ts/skeleton.lua.gsp b/src/blog/nvim-ts/skeleton.lua.gsp
new file mode 100644
index 0000000..0bc5f2e
--- /dev/null
+++ b/src/blog/nvim-ts/skeleton.lua.gsp
@@ -0,0 +1,14 @@
+@span .kw {-local} @span .kw {-function} @span .fn {-map}(@span .var {-lhs}, @span .var {-rhs})
+ vim.keymap.@span .fn {-set}(@span .str {-'n'}, @span .var {-lhs}, @span .kw {-function}()
+ @span .cmt {--- TODO}
+ @span .kw {-end}, {
+ @span .var {-buffer} @span .op {-=} @span .cnst {-true},
+ @span .var {-noremap} @span .op {-=} @span .cnst {-true},
+ @span .var {-silent} @span .op {-=} @span .cnst {-true},
+ \})
+@span .kw {-end}
+
+@span .fn {-map}(@span .str {-'p'}, @span .str {-'pick'})
+@span .fn {-map}(@span .str {-'r'}, @span .str {-'reword'})
+@span .fn {-map}(@span .str {-'s'}, @span .str {-'squash'})
+@span .fn {-map}(@span .str {-'f'}, @span .str {-'fixup'})
diff --git a/src/blog/nvim-ts/ts-tree.scm.gsp b/src/blog/nvim-ts/ts-tree.scm.gsp
new file mode 100644
index 0000000..bc2840a
--- /dev/null
+++ b/src/blog/nvim-ts/ts-tree.scm.gsp
@@ -0,0 +1,12 @@
+(@span .fn {-operation}) @span .cmt {-; [1:1 - 30]}
+ (@span .fn {-command}) @span .cmt {-; [1:1 - 4]}
+ (@span .fn {-label}) @span .cmt {-; [1:6 - 12]}
+ (@span .fn {-message}) @span .cmt {-; [1:14 - 30]}
+(@span .fn {-operation}) @span .cmt {-; [2:1 - 30]}
+ (@span .fn {-command}) @span .cmt {-; [2:1 - 4]}
+ (@span .fn {-label}) @span .cmt {-; [2:6 - 12]}
+ (@span .fn {-message}) @span .cmt {-; [2:14 - 30]}
+(@span .fn {-operation}) @span .cmt {-; [3:1 - 29]}
+ (@span .fn {-command}) @span .cmt {-; [3:1 - 4]}
+ (@span .fn {-label}) @span .cmt {-; [3:6 - 12]}
+ (@span .fn {-message}) @span .cmt {-; [3:14 - 29]}