diff options
| author | Thomas Voss <mail@thomasvoss.com> | 2023-11-14 18:10:22 +0100 | 
|---|---|---|
| committer | Thomas Voss <mail@thomasvoss.com> | 2023-11-14 18:10:22 +0100 | 
| commit | ea77956c0224d9f9997d60e91642a4e255750f5b (patch) | |
| tree | 7d47222427808b35f9fe07fb5e3249d08fadf109 /src/blog/nvim-ts | |
| parent | d31314638a4499ce36def937b61d2069f496eaaa (diff) | |
Add a new blog post
Diffstat (limited to 'src/blog/nvim-ts')
| -rw-r--r-- | src/blog/nvim-ts/change-command.diff.gsp | 9 | ||||
| -rw-r--r-- | src/blog/nvim-ts/final.lua.gsp | 28 | ||||
| -rw-r--r-- | src/blog/nvim-ts/final.webm | bin | 0 -> 207067 bytes | |||
| -rw-r--r-- | src/blog/nvim-ts/get-cursor.diff.gsp | 12 | ||||
| -rw-r--r-- | src/blog/nvim-ts/get-parent.diff.gsp | 14 | ||||
| -rw-r--r-- | src/blog/nvim-ts/git-log-2.gsp | 11 | ||||
| -rw-r--r-- | src/blog/nvim-ts/git-log.gsp | 17 | ||||
| -rw-r--r-- | src/blog/nvim-ts/git-rebase-2.gsp | 3 | ||||
| -rw-r--r-- | src/blog/nvim-ts/git-rebase.gsp | 34 | ||||
| -rw-r--r-- | src/blog/nvim-ts/index.gsp | 226 | ||||
| -rw-r--r-- | src/blog/nvim-ts/skeleton.lua.gsp | 14 | ||||
| -rw-r--r-- | src/blog/nvim-ts/ts-tree.scm.gsp | 12 | 
12 files changed, 380 insertions, 0 deletions
| 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.webmBinary files differ new 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.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]} |