<rss version="2.0"><channel><title>chris-kipp.io Blog</title><link>https://www.chris-kipp.io/blog</link><description>Blog of Chris Kipp. Mostly thoughts on Scala tooling, Neovim, and music I like.</description><lanugage>en-us</lanugage><category>Blog</category><lastBuildDate>Wed, 13 Aug 2025 00:00:00 +0200</lastBuildDate><item><title>Claude Code, Metals, and NVIM</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;A look at a terminal based AI powered workflow with claude code and&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Claude Code, Metals, and NVIM&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;A look at a terminal based AI powered workflow with claude code and&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Claude Code, Metals, and NVIM&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#an-overview-of-using-claude-code-metals-and-nvim&quot; id=&quot;an-overview-of-using-claude-code-metals-and-nvim&quot;&gt;An overview of using Claude Code, Metals, and NVIM&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I hated AI assisted development for a long time.&lt;/p&gt;
&lt;p&gt;I remember when Copilot was first released and people lost their minds. This was
apparently the beginning of the end for human developers. The next couple years
new tools came out constantly promising a revolution in developer productivity
and replacement... I hated it. All my initial attempts at using these tools were
awful. Either I felt like I wasn't in control, the editor integrations for nvim
were terrible, or I was just unsatisfied with the results. When new promising
editors were released that were more AI-centric I couldn't find it in myself to
pull away from a setup that I felt so invested and productive in.&lt;/p&gt;
&lt;p&gt;I started a new job earlier this year that is quite AI-centric in their approach
to development, not in an overpowering hype-driven way, but in a &lt;em&gt;this should
make you more productive... use it&lt;/em&gt; sort of way. While skeptical at first, I've
found in the past few months that I've landed on a flow developing Scala that
I'm quite happy with that allows me to stay in my comfortable nvim/tmux/terminal
environment while benefiting from assistance that still allows me to feel like
I'm in control and boosts my productivity.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#my-setup&quot; id=&quot;my-setup&quot;&gt;My Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I still live in the terminal for development. On a daily basis developing Scala
I'm using &lt;a href=&quot;https://github.com/scalameta/metals&quot;&gt;Metals&lt;/a&gt; through
&lt;a href=&quot;https://github.com/scalameta/nvim-metals&quot;&gt;nvim-metals&lt;/a&gt;. I heavily use &lt;a href=&quot;https://github.com/anthropics/claude-code&quot;&gt;claude
code&lt;/a&gt; with Metals as an MCP server,
and that's what I'd like to talk about and illustrate here. If you're not
familiar with MCP servers, the basic idea is that they expose a set of tools
that can be used from your agent when the deem it appropriate.&lt;/p&gt;
&lt;p&gt;As of Metals &lt;a href=&quot;https://scalameta.org/metals/blog/2025/05/13/strontium&quot;&gt;v1.5.3&lt;/a&gt;
Metals can act as an &lt;a href=&quot;https://modelcontextprotocol.io/overview&quot;&gt;MCP Server&lt;/a&gt;.
This matters because it can greatly speed up your workflow for some of the most
common things you'll find claude doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;make sure it quickly and accurately finds what it needs in your code base&lt;/li&gt;
&lt;li&gt;making sure you code compiles after it changes it&lt;/li&gt;
&lt;li&gt;make sure you tests pass after your changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I get giddy about protocols and things working together, and I love the way that
this ends up working on the hood. Let's assume a couple things in the following
flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You're using your build tool as your server (let's say sbt). This matters
because whether you're doing work in your editor, running things from the
tool from the command line, or having Metals compile your code for you via the
MCP tools, your code is always in the same compilation state. I'll illustrate
this down below further. If you didn't know there is a setting to tell
Metals to default to this if your build tool supports it instead of relying on
bloop. In your metals settings just set &lt;code&gt;defaultBspToBuildTool&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. I
recommend you do this regardless of whether your using Metals as an MCP server
or not.&lt;/li&gt;
&lt;li&gt;You're starting Metals as an MCP server. You need to set &lt;code&gt;startMcpServer&lt;/code&gt; to
&lt;code&gt;true&lt;/code&gt; in your settings for this.&lt;/li&gt;
&lt;li&gt;This example uses claude code, so in order for Metals to write the correct
config file so claude picks it up when you start it, you'll want to set
&lt;code&gt;mcpClient&lt;/code&gt; to &lt;code&gt;claude&lt;/code&gt; in your settings.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While I use other MCP tools at work, we'll focus on this flow to illustrate the
benefits of Metals here. If you have everything setup like I do above when you
start Metals by opening your project you should be greeted with a nice little
message like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;LSP[metals][Info] Metals MCP server started on port: 64402. Refresh connection if needed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then if you start &lt;code&gt;claude&lt;/code&gt; in the root of that same directory you should
see a little prompt with the message:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;New MCP server found in .mcp.json: metals 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then you need to respond if you want to allow it or not. This choice is
saved then for your workspace.&lt;/p&gt;
&lt;p&gt;It knows to start this because you'll notice a newly create &lt;code&gt;.mcp.json&lt;/code&gt; file at
the root of your project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;mcpServers&amp;quot;: {
    &amp;quot;metals&amp;quot;: {
      &amp;quot;url&amp;quot;: &amp;quot;http://localhost:54640/sse&amp;quot;,
      &amp;quot;type&amp;quot;: &amp;quot;sse&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;#letting-metals-do-its-thing&quot; id=&quot;letting-metals-do-its-thing&quot;&gt;Letting Metals do its thing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;for these examples i'll use the &lt;a href=&quot;https://github.com/scalameta/metals&quot;&gt;metals&lt;/a&gt;
codebase, which isn't huge, but it illustrates this well. let's pretend that you
are using another editor that doesn't use bsp to edit your code meaning that
while you're editing your code it's fine, but if you switch to sbt, you need to
re-compile your code. the same can be said for using bsp with bloop instead of
sbt as well. even though your code is fully compiled with metals and with bloop,
when you drop down into sbt you need to recompile your code. in my opinion this
is one of the biggest benefits of defaulting to your build tool to be your build
server: you don't need to pay that cost of compiling twice. so down below, what
you're seeing is claude issuing a command to compiling your code with sbt.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;⏺ The compilation completed but with some errors at the end. Here are the results:

  Total compilation time: 48.052 seconds (48s)

  The compilation mostly succeeded - it compiled all the main modules including:
  - mtags-interfaces
  - sbt-metals
  - mtags-shared
  - mtags-java
  - mtags
  - metals (main module)
  - tests modules
  - metals-docs
  - metals-bench

  The timing breakdown shows:
  - Real time: 48.052 seconds
  - User CPU time: 131.41 seconds
  - System CPU time: 7.94 seconds
  - CPU utilization: 289% (indicating parallel compilation)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However realistically part of your code will have already been compiled, but so
incrementally compilation will have kicked in and it will be a bit faster than
this. I'll have claude just add a single method to a file and then recompile and
report back.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;⏺ Incremental compilation time: 7.307 seconds (7s)

  The incremental compilation was much faster! Here's the comparison:

  - Full compilation (first run): 48.052 seconds
  - Incremental compilation (after small change): 7.307 seconds

  The incremental compilation only needed to recompile:
  - 1 Scala source in the metals module (the Time.scala file I changed)
  - 3 Scala sources in the metals module (likely dependent files)
  - 1 Scala source in the tests/unit module (probably a test that uses Time)

  This demonstrates SBT's incremental compilation working effectively - it only recompiled the changed file and its dependents, rather than the entire codebase, resulting in
  an 85% reduction in compile time.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now realistically it's fair to say that it'll be changing more code than this.
So we'll use 7 seconds as the bottom datapoint to prove the point that every
time claude is dropping down to issue a bash command to re-compile your code
it's going to at least take this much time.&lt;/p&gt;
&lt;p&gt;Now if you're using Metals as your LSP server with MCP enabled and it's hooked
up to claude every change that claude is making to your codebase ends up being
registered and accounted for (although this does see to have slightly
differently behavior depending on your editor for some reason if you read the
issue &lt;a href=&quot;https://github.com/scalameta/metals/issues/7662&quot;&gt;here&lt;/a&gt;). In general
Metals doesn't just rely on notifications from the LSP client about file changes
but it also has it's own file watcher. When Metals changes a file even without
any LSP notification being sent this basic flow will happen:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;OS detects file change → PathWatcher receives event&lt;/li&gt;
&lt;li&gt;File filter check → watchFilter(path) validates if file should trigger events&lt;/li&gt;
&lt;li&gt;Event queued → FileWatcherEvent.createOrModify(path) added to BlockingQueue&lt;/li&gt;
&lt;li&gt;Callback triggered → didChangeWatchedFiles(event) called&lt;/li&gt;
&lt;li&gt;onCreate invoked → onCreate(path) in MetalsLspService.scala&lt;/li&gt;
&lt;li&gt;Compilation triggered → compilers.didChange(path, false) in MetalsLspService.scala&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So as claude is making changes your Metals state is consistently compiling and
being updated to match what's happening on disk. Due to this if you get to a
point where claude tries to compile your code it's already fully compiled and
should basically return almost instantaneous with either no errors letting you
know your code is fine or with diagnostics about what is wrong. The MCP logs
for this looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[Trace - 18:16:29]  Sending request
{&amp;quot;method&amp;quot;:&amp;quot;tools/call&amp;quot;,&amp;quot;params&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;compile-full&amp;quot;,&amp;quot;arguments&amp;quot;:{},&amp;quot;_meta&amp;quot;:{&amp;quot;claudecode/toolUseId&amp;quot;:&amp;quot;toolu_01ENHHfWxBMUZXTWFxEyASyg&amp;quot;}},&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:2}
[Trace - 18:16:29]  Received response
event: message

[Trace - 18:16:29]  Received response
data: {&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:2,&amp;quot;result&amp;quot;:{&amp;quot;content&amp;quot;:[{&amp;quot;type&amp;quot;:&amp;quot;text&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Compilation successful.&amp;quot;}],&amp;quot;isError&amp;quot;:false}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you have issues:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[Trace - 18:17:08]  Sending request
{&amp;quot;method&amp;quot;:&amp;quot;tools/call&amp;quot;,&amp;quot;params&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;compile-full&amp;quot;,&amp;quot;arguments&amp;quot;:{},&amp;quot;_meta&amp;quot;:{&amp;quot;claudecode/toolUseId&amp;quot;:&amp;quot;toolu_01DJYsYhGJbh6KQm2nbZ9nWX&amp;quot;}},&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:3}
[Trace - 18:17:08]  Received response
event: message

[Trace - 18:17:08]  Received response
data: {&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:3,&amp;quot;result&amp;quot;:{&amp;quot;content&amp;quot;:[{&amp;quot;type&amp;quot;:&amp;quot;text&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Compilation failed with errors:\nmetals/src/main/scala/scala/meta/internal/metals/watcher/FileWatcherEvent.scala L13-L13:\n'=' expected.&amp;quot;}],&amp;quot;isError&amp;quot;:false}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, in both of these scenarios the compile command was basically a no-op and
Metals was able to return immediately with information so that claude can either
move forward with validation knowing nothing is wrong, or to start and fix
whatever issue it introduced. The time this alone saves in a long editing
session is immense.&lt;/p&gt;
&lt;p&gt;This same concept can be applied for testing. If your codebase is already fully
compiled then starting a test should also basically be instantaneous.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;gt; Can you run the ScalaVersionsSuite test with Metals

⏺ metals - test (MCP)(testClass: &amp;quot;tests.ScalaVersionsSuite&amp;quot;)
  ⎿  
     tests.ScalaVersionsSuite:
       + idempotent-minor-release passed
     … +55 lines (ctrl+r to expand)

⏺ All 54 tests in ScalaVersionsSuite passed successfully. The test suite completed in 42ms with
  no failures.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Behind the scenes the request in MCP looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[Trace - 18:20:43]  Sending request
{&amp;quot;method&amp;quot;:&amp;quot;tools/call&amp;quot;,&amp;quot;params&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;test&amp;quot;,&amp;quot;arguments&amp;quot;:{&amp;quot;testClass&amp;quot;:&amp;quot;tests.ScalaVersionsSuite&amp;quot;},&amp;quot;_meta&amp;quot;:{&amp;quot;claudecode/toolUseId&amp;quot;:&amp;quot;toolu_01BXjFLMVkLjAEQuwwjAobSh&amp;quot;}},&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:4}
[Trace - 18:20:43]  Received response
event: message

[Trace - 18:20:43]  Received response
data: {&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:4,&amp;quot;result&amp;quot;:{&amp;quot;content&amp;quot;:[{&amp;quot;type&amp;quot;:&amp;quot;text&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;\ntests.ScalaVersionsSuite:\n  + idempotent-minor-release passed\n  + retain-pre-release-version passed\n  + drop-typelevel-vendor-suffix passed\n  + recommended-future passed\n  + recommended-212 passed\n  + recommended-211 passed\n  + recommended-213 passed\n  + future-213 passed\n  + not-future-213 passed\n  + future-212 passed\n  + not-future-212 passed\n  + not-future-211 passed\n  + future-214 passed\n  + future-315 passed\n  + 2.12.11-comapatible-with-2.12.5 passed\n  + 2.12.5-not-compatible-with-2.12.11 passed\n  + 2.12.7-compatible-with-2.12.5 passed\n  + 2.12.5-not-compatible-with-2.12.7 passed\n  + 2.12.11-compatible-with-2.11.12 passed\n  + 2.11.12-not-compatible-with-2.12.11 passed\n  + 0.9.10-not-compatible-with-0.10.0-M4 passed\n  + 0.9.10-not-compatible-with-0.10.0-RC2 passed\n  + recommended-3 passed\n  + compare-3.0.0-M1&amp;lt;=3.0.0-M2 passed\n  + compare-3.0.0-M2&amp;gt;=3.0.0-M1 passed\n  + compare-3.0.0-RC1&amp;lt;=3.0.0-RC2 passed\n  + compare-3.0.0-RC2&amp;gt;=3.0.0-RC1 passed\n  + compare-3.0.0-RC1&amp;lt;=3.0.0 passed\n  + compare-3.0.0&amp;gt;=3.0.0-RC1 passed\n  + compare-3.0.0-M1&amp;lt;=3.0.0 passed\n  + compare-3.0.0&amp;gt;=3.0.0-M1 passed\n  + compare-3.0.0-RC1&amp;lt;3.0.0 passed\n  + compare-3.0.0&amp;gt;3.0.0-RC1 passed\n  + compare-3.0.0-M1&amp;lt;3.0.0 passed\n  + compare-3.0.0&amp;gt;3.0.0-M1 passed\n  + compare-3.0.0-M1&amp;lt;3.0.0-RC1 passed\n  + compare-3.0.0-RC1&amp;gt;3.0.0-M1 passed\n  + compare-RC1&amp;lt;=RC1-SNAPSHOT passed\n  + compare-RC2&amp;gt;=RC1-SNAPSHOT passed\n  + compare-RC1&amp;lt;RC2-SNAPSHOT passed\n  + compare-RC2&amp;gt;RC1-SNAPSHOT passed\n  + compare-NIGTLY passed\n  + not-future-3-M1 passed\n  + not-future-3-M2 passed\n  + not-future-RC passed\n  + smth-library_2.13-21.2.0-sources.jar passed\n  + scala3-compiler_3-3.0.1-RC2-bin-20210310-4af1386-NIGHTLY-sources.jar passed\n  + scala3-library_3-3.1.0-RC1.jar passed\n  + scala-library-2.13.1.jar passed\n  + cool4.4_2.13-3.0.jar passed\n  + scala3-library_3-3.0.0-sources.jar passed\n  + munit_3-0.7.29-sources.jar passed\n  + tested-3.0-sources.jar passed\n  + newer-RC passed\nExecution took 42ms\n54 tests, 54 passed, 0 failed, 0 skipped\n&amp;quot;}],&amp;quot;isError&amp;quot;:false}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cool (or not so cool if you hate how many moving pieces there are) thing is
that the Debug Adapter Protocol (DAP) is also being used here behind the scenes.
When this request comes into Metals the flow is:&lt;/p&gt;
&lt;p&gt;The flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Claude Request → MCP (mcp__metals__test tool)&lt;/li&gt;
&lt;li&gt;MCP → Metals (McpTestRunner.runTests)&lt;/li&gt;
&lt;li&gt;Metals → DAP (TestSuiteDebugAdapter creation)&lt;/li&gt;
&lt;li&gt;DAP → Build Server (BSP test execution request)&lt;/li&gt;
&lt;li&gt;Build Server executes tests via sbt/mill/whatever build server you're using&lt;/li&gt;
&lt;li&gt;Build Server → DAP (test results)&lt;/li&gt;
&lt;li&gt;DAP → Metals (formatted results)&lt;/li&gt;
&lt;li&gt;Metals → MCP (test summary)&lt;/li&gt;
&lt;li&gt;MCP → Claude (final response)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or visually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌──────────────┐
│ Claude  │───▶│   MCP   │───▶│ Metals  │───▶│   DAP   │───▶│ Build Server │
│ Request │    │Tool Call│    │  LSP    │    │ Adapter │    │     (BSP)    │
└─────────┘    └─────────┘    └─────────┘    └─────────┘    └──────────────┘
     ▲              ▲              ▲              ▲                  │
     │              │              │              │                  │
     │              │              │              │                  ▼
     │              │              │              │         ┌──────────────┐
     │              │              │              │         │ Test Run     │
     │              │              │              │         │   (sbt/etc)  │
     │              │              │              │         └──────────────┘
     │              │              │              │                  │
     │              │              │              │                  │
┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐             │
│ Claude  │◀───│   MCP   │◀───│ Metals  │◀───│   DAP   │◀────────────┘
│Response │    │Response │    │ Results │    │ Results │
└─────────┘    └─────────┘    └─────────┘    └─────────┘

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These two feature alone have saved me a ton of time. There are also more tools
that can be used in a variety of ways. If you're using claude you can view these
via &lt;code&gt;/mcp&lt;/code&gt; and selecting metals. The existing tools as of today are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;compile-file&lt;/li&gt;
&lt;li&gt;compile-module&lt;/li&gt;
&lt;li&gt;compile-full&lt;/li&gt;
&lt;li&gt;test&lt;/li&gt;
&lt;li&gt;glob-search&lt;/li&gt;
&lt;li&gt;typed-glob-search&lt;/li&gt;
&lt;li&gt;inspect&lt;/li&gt;
&lt;li&gt;get-docs&lt;/li&gt;
&lt;li&gt;get-usages&lt;/li&gt;
&lt;li&gt;import-build&lt;/li&gt;
&lt;li&gt;find-dep&lt;/li&gt;
&lt;li&gt;list-modules&lt;/li&gt;
&lt;li&gt;format-file&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Many of these can be used either as alternatives to things like web-search for
finding deps, hooks for formatting, or finding actual usages of symbols across
your codebase in a way that can not only speed up your workflow, but provide you
a more accurate one.&lt;/p&gt;
&lt;p&gt;To summarize, if you use Metals and you use a tool like claude, use Metals as an
MCP serer. It will save you some time.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/claude-code-metals-and-nvim</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/claude-code-metals-and-nvim</guid><pubDate>Wed, 13 Aug 2025 00:00:00 +0200</pubDate></item><item><title>An intro to Flow Fields in Scala</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;A look at flow fileds using Scala and following the examples in https://tylerxhobbs.com/essays/2020/flow-fields&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;An intro to Flow Fields in Scala&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;A look at flow fileds using Scala and following the examples in https://tylerxhobbs.com/essays/2020/flow-fields&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;An intro to Flow Fields in Scala&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#an-intro-to-flow-fields-in-scala&quot; id=&quot;an-intro-to-flow-fields-in-scala&quot;&gt;An intro to Flow Fields in Scala&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This article is an interpretation in Scala of the wonderful &lt;a href=&quot;https://tylerxhobbs.com/essays/2020/flow-fields&quot;&gt;Flow Fields
article&lt;/a&gt; by &lt;a href=&quot;https://tylerxhobbs.com/about&quot;&gt;Tyler
Hobbs&lt;/a&gt;. Getting started in generative art can be
difficult and intimidating, and I'm extremely grateful for artists like Tyler
that open up their process for others to either learn it for themselves or to
just help admirers better understand the process. Lately I've been attempting to
go from the &amp;quot;admirer&amp;quot; to the &amp;quot;learner&amp;quot;, so I was drawn to this article since it
not only gives a clear introduction to Flow Fields, but also uses pseudocode to
illustrate the concepts. Since I primarily code in Scala, my goal with this post
was to really understand the concepts by translating his pseudocode into Scala
code utilizing the Java &lt;a href=&quot;https://processing.org/&quot;&gt;Processing&lt;/a&gt; library. I'll also
add in some extra resources to help fill in some gaps if you're new to Flow
Fields, like I am. When you see quotes, unless noted otherwise, they're quotes
from his article.&lt;/p&gt;
&lt;p&gt;If you're curious to try running some of this code, I have each stage capture in
&lt;a href=&quot;https://github.com/ckipp01/an-intro-to-flow-fields-in-scala&quot;&gt;this repo&lt;/a&gt; with
instructions on how to run them. If having a fresh palette to start from is more
your thing, check out my
&lt;a href=&quot;https://www.foundweekends.org/giter8/index.html&quot;&gt;Giter8&lt;/a&gt; template
&lt;a href=&quot;https://github.com/ckipp01/scala-processing-giter8&quot;&gt;here&lt;/a&gt; that can be used to
quickly get you started with Scala and processing. Processing 4 isn't published
to Maven Central, so this will basically do some necessary setup for you to use
Processing via Scala.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#a-grid-of-angles&quot; id=&quot;a-grid-of-angles&quot;&gt;A Grid of Angles&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Flow fields are based around a grid. Roughly speaking, this grid covers the
whole image/canvas. At each point in the grid, you store an angle.  Your grid
should be stored as a 2D array of floating point numbers. Each item in the
array holds an angle and represents a point in the grid.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Just like the article, we'll take this step by step taking the pseudocode and
translating it to Scala. While the pictures will for sure help illustrate the
steps, if you're more of a visual person and would rather watch a video to fully
introduce the concepts before we dive in, I can highly recommend &lt;a href=&quot;https://youtu.be/na7LuZsW2UM&quot;&gt;this
video&lt;/a&gt; by &lt;a href=&quot;https://www.youtube.com/@ChrisCourses&quot;&gt;Chris
Courses&lt;/a&gt;. It does a fantastic job of
going through many of the concepts covered in this article.&lt;/p&gt;
&lt;p&gt;Now, to start this out, we'll do just like the article says and we'll initialize
a grid. The grid will be 1000 x 1000, but in reality we'll make some extra
margin around the image so you don't see everything abruptly end at the edge of
our screen. The idea here is to just visualize the grid that we've created as
well as a default angle that we'll give to every section in the grid (&lt;code&gt;Pi * 0.25&lt;/code&gt;). We'll draw an arrow to visualize this angle.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;import processing.core
import processing.core.*
import processing.core.PApplet
import scala.math.Pi

class One extends PApplet:
  // The actual size of our canvas
  val Width = 1000
  val Height = 1000

  // The actual size we'll work with for our flows with extra margin
  val leftX = (Width * -0.5).toInt
  val rightX = (Width * 1.5).toInt
  val topY = (Height * -0.5).toInt
  val bottomY = (Height * 1.5).toInt

  // The resolution, which will impact how many cols and rows are on the canvas
  val resolution = (Width * 0.01).toInt

  val numCols = (rightX - leftX) / resolution
  val numRows = (bottomY - topY) / resolution
  val grid = Array.ofDim[Double](numCols, numRows)

  override def settings(): Unit =
    size(Width, Height)

  override def draw(): Unit =
    background(255)

    // Drawing out all the arrows in the grid
    for col &amp;lt;- 0 until numCols do
      for row &amp;lt;- 0 until numRows do
        val x = (col * resolution)
        val y = (row * resolution)
        val angle = Pi * 0.25
        drawArrow(x, y, angle, resolution - 2)

  /** Given x and y coordinates we &amp;quot;GOTO&amp;quot; that part of the grid and then draw an
    * arrow showing the given angle.
    */
  def drawArrow(x: Double, y: Double, angle: Double, len: Double): Unit =
    pushMatrix()

    translate(x.toFloat, y.toFloat)
    rotate(angle.toFloat)

    val arrowSize = 2
    val lineLength = len.toFloat - arrowSize

    line(0, 0, lineLength, 0)

    triangle(
      lineLength,
      0,
      lineLength - arrowSize,
      (arrowSize / 2).toFloat,
      lineLength - arrowSize,
      (-arrowSize / 2).toFloat
    )

    popMatrix()
end One
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just to fill in a possible gap, a popular thing you'll see is that often when
drawing a shape on the grid you won't actually store every point at each
location on the grid. Instead, you'll store the points to draw the shape in a
single location on the grid, and then move the grid around continually drawing
the same shape. This is what you're seeing with the &lt;code&gt;pushMatrix()&lt;/code&gt; and
&lt;code&gt;popMatrix()&lt;/code&gt; in the &lt;code&gt;drawArrow&lt;/code&gt; method. You can read more about this &lt;a href=&quot;https://processing.org/tutorials/transform2d&quot;&gt;here in
this processing tutorial about 2D
transformations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When ran, the image produced will look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/alM7rtK.png&quot; alt=&quot;flow-fields one&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Generated from &lt;a href=&quot;https://github.com/ckipp01/an-intro-to-flow-fields-in-scala/blob/main/One.scala&quot;&gt;&lt;code&gt;One.scala&lt;/code&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, all these arrow are just pointing in the same direction, which isn't what
we want. So we'll introduce a slight turn as we iterate throw all the rows. The
for loop we have that calls &lt;code&gt;drawArrow&lt;/code&gt; can be changed slightly to do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;for col &amp;lt;- 0 until numCols do
  for row &amp;lt;- 0 until numRows do
    val x = (col * resolution)
    val y = (row * resolution)
-   val angle = Pi * 0.25
+   val angle = ((row.toDouble / numRows.toDouble) * Pi)
    drawArrow(x, y, angle, resolution - 2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ran with this change, you'll now see the arrows curving:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/mUUKIQv.png&quot; alt=&quot;flow-fieds two&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Generated from &lt;a href=&quot;https://github.com/ckipp01/an-intro-to-flow-fields-in-scala/blob/main/Two.scala&quot;&gt;&lt;code&gt;Two.scala&lt;/code&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a href=&quot;#drawing-curves-through-the-field&quot; id=&quot;drawing-curves-through-the-field&quot;&gt;Drawing Curves through the Field&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The next step of this is to use this curve to guide us in drawing a line
following the flow of the arrows. To make this a bit easier for the moment we'll
simply plot a dot in red to easily show where the line would be drawn. In order
to make this work we change the code around a bit to no longer just draw out the
angle when we iterate over it, but instead we store the angle in the grid
because we need to iterate over the grid again to draw the dots. The full code
for this looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;import processing.core
import processing.core.*
import processing.core.PApplet
import scala.math.Pi
import scala.math.cos
import scala.math.sin

class Three extends PApplet:
  // The actual size of our canvas
  val Width = 1000
  val Height = 1000

  // The actual size we'll work with for our flows with extra margin
  val leftX = (Width * -0.5).toInt
  val rightX = (Width * 1.5).toInt
  val topY = (Height * -0.5).toInt
  val bottomY = (Height * 1.5).toInt

  // The resolution, which will impact how many cols and rows are on the canvas
  val resolution = (Width * 0.01).toInt

  val numCols = (rightX - leftX) / resolution
  val numRows = (bottomY - topY) / resolution
  val grid = Array.ofDim[Double](numCols, numRows)

  override def settings(): Unit =
    size(Width, Height)

  override def draw(): Unit =
    background(255)

    for col &amp;lt;- 0 until numCols do
      for row &amp;lt;- 0 until numRows do
        grid(col)(row) = ((row.toDouble / numRows.toDouble) * Pi)

    for col &amp;lt;- 0 until numCols do
      for row &amp;lt;- 0 until numRows do
        drawArrow(
          col * resolution,
          row * resolution,
          grid(col)(row),
          resolution - 2
        )

    var x: Double = 500
    var y: Double = 0
    val stepLength = 10
    val steps = 300

    for step &amp;lt;- 0 until steps do
      drawPoint(x, y)
      val xOffset: Double = x - leftX
      val yOffset: Double = y - topY

      val columnIndex: Int = (xOffset / resolution).toInt
      val rowIndex: Int = (yOffset / resolution).toInt

      if (columnIndex &amp;gt; 0 &amp;amp;&amp;amp; columnIndex &amp;lt; grid.length)
        &amp;amp;&amp;amp; (rowIndex &amp;gt; 0 &amp;amp;&amp;amp; rowIndex &amp;lt; grid(columnIndex).length)
      then
        val gridAngle: Double = grid(columnIndex)(rowIndex)

        val xStep: Double = stepLength * cos(gridAngle)
        val yStep: Double = stepLength * sin(gridAngle)

        x += xStep
        y += yStep

  end draw

  def drawPoint(x: Double, y: Double): Unit =
    val baseWeight = g.strokeWeight
    val baseStroke = g.strokeColor
    stroke(255, 0, 0)
    strokeWeight(10)
    point(x.toFloat, y.toFloat)
    strokeWeight(baseWeight)
    stroke(baseStroke)

  /** Given x and y coordinates we &amp;quot;GOTO&amp;quot; that part of the grid and then draw an
    * arrow showing the given angle.
    */
  def drawArrow(x: Double, y: Double, angle: Double, len: Double): Unit =
    pushMatrix()
    translate(x.toFloat, y.toFloat)
    rotate(angle.toFloat)

    val arrowSize = 2
    val lineLength = len.toFloat - arrowSize
    line(0, 0, lineLength, 0)

    triangle(
      lineLength,
      0,
      lineLength - arrowSize,
      (arrowSize / 2).toFloat,
      lineLength - arrowSize,
      (-arrowSize / 2).toFloat
    )

    popMatrix()

end Three
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the code for this is ran it will produce an image that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/N8E3kNV.png&quot; alt=&quot;flow-fields three&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Generated from &lt;a href=&quot;https://github.com/ckipp01/an-intro-to-flow-fields-in-scala/blob/main/Three.scala&quot;&gt;&lt;code&gt;Three.scala&lt;/code&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are a few different important things that you can change here to make
quite a bit of difference in how your line is drawn. From the article:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We need to pick values for a few key parameters of how we draw the curves: the
step_length, num_steps, and the starting position (x, y). The simplest is the
step_length. Typically, this should be small enough that you don't see any
sharp points on the curve. For me, that's usually around 0.1% to 0.5% of the
image width. I go larger for quicker render speeds, and smaller if there are
tight turns that need to be clean.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So in our example &lt;code&gt;step_length&lt;/code&gt; (aka &lt;code&gt;stepLength&lt;/code&gt;) is set to 10 resulting in the
dots being pretty close together. If we were to change this to say, 30, you can
tell the difference right away.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/rMWVx18.png&quot; alt=&quot;flow-fields three-a&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Notice how much farther apart the dots are now, which will end up resulting in a
choppier line when traced.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;num_steps&lt;/code&gt; (&lt;code&gt;steps&lt;/code&gt;) controls the amount of steps you'll take. So for
example if you have a short step length and a short amount of steps it'll result
in a short choppy line like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/5FPFWHy.png&quot; alt=&quot;flow-fields three-b&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Finally, the starting positions &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; also make a difference since...
that's where your line is starting from. In our example we're starting at 500
for the x axis and 0 for the y which makes the line start nice and center at the
top of the grid. However, we could shift this down to be 300 for the y axis and
you'll notice the original image then changes to this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/klEOPpD.png&quot; alt=&quot;flow-fields three-c&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With these three values alone you start to see how you can control what each
line looks like. Whether it's short, long, choppy, smooth, and where it's
positioned can all make a pretty large difference in the end result. The
original article gives some great illustrations of these differences at a larger
scale.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#distorting-the-vectors&quot; id=&quot;distorting-the-vectors&quot;&gt;Distorting the Vectors&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;One big design decision is how you want to distort the vectors in your field.
The method you use for this will determine what shapes your curves take. It
will determine whether there are loops, abrupt turns, and overlapping lines.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;About 90% of the time, people use Perlin noise to initialize the vectors. This
is handy and easy, because it gives you smooth, continuous values across the
2D plane.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Perlin noise is exactly what we're going to use here. It's so popular in fact
that processing has built-in support for it via &lt;code&gt;noise&lt;/code&gt;. The change we need to
do here to actually implement this is shockingly small.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;for col &amp;lt;- 0 until numCols do
  for row &amp;lt;- 0 until numRows do
+    val scaledX = col * 0.005
+    val scaledY = row * 0.005
+    val angle = noise(scaledX.toFloat, scaledY.toFloat) * Pi * 2
-    grid(col)(row) = ((row.toDouble / numRows.toDouble) * Pi)
+    grid(col)(row) = angle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When running with this change, you'll generate an image similar to this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/fMD01r8.png&quot; alt=&quot;flow-fields four&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Generated from &lt;a href=&quot;https://github.com/ckipp01/an-intro-to-flow-fields-in-scala/blob/main/Four.scala&quot;&gt;&lt;code&gt;Four.scala&lt;/code&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, keep in mind now that even though your starting points may always be
the same, the angles will not be due to the &amp;quot;controlled randomness&amp;quot; of the
perlin noise.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#putting-it-all-together&quot; id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point we actually have most of what we need to really start generating
some things. Now that we understand the grid behind what we're doing the first
thing we can do is get rid of generating it. Just remember that it's always
there doing the same thing, but we don't need to generate out the arrows.  We
can do this by removing the &lt;code&gt;drawArrow&lt;/code&gt; method and also the for loop that calls
it as we iterate through the columns and rows.&lt;/p&gt;
&lt;p&gt;No more arrows!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/YVPxJSu.png&quot; alt=&quot;flow-fields five&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Generated from &lt;a href=&quot;https://github.com/ckipp01/an-intro-to-flow-fields-in-scala/blob/main/Five.scala&quot;&gt;&lt;code&gt;Five.scala&lt;/code&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Also currently we have a hard coded start &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; point which we don't
really want. So instead what we can do is refactor this out to just define a
&lt;code&gt;steps&lt;/code&gt;, the &lt;code&gt;stepLength&lt;/code&gt; and also a new &lt;code&gt;lineCount&lt;/code&gt; for the amount of lines
we'd like to draw. Then we can randomly create the starting &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; for
every line. This sounds great, but it will introduce a new problem. Firstly, if
we run it, it will look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/eYcXIit.gif&quot; alt=&quot;flow-fields six&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Generated from &lt;a href=&quot;https://github.com/ckipp01/an-intro-to-flow-fields-in-scala/blob/main/Six.scala&quot;&gt;&lt;code&gt;Six.scala&lt;/code&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You'll notice that instead of just a single image, it will keep regenerating. We
first notice this now because we are introducing randomness into the generation
that changes on every &amp;quot;tick&amp;quot;. We can prevent this by using &lt;code&gt;noLoop&lt;/code&gt;. Also, all
of the dots just look chaotic. It's hard to see any pattern here, so we'll
instead switch to actually drawing a line.&lt;/p&gt;
&lt;p&gt;At this point our full code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;import processing.core
import processing.core.*
import processing.core.PApplet
import scala.math.Pi
import scala.math.cos
import scala.math.sin
import scala.util.Random

class Seven extends PApplet:
  // The actual size of our canvas
  val Width = 1000
  val Height = 1000

  // The actual size we'll work with for our flows with extra margin
  val leftX = (Width * -0.5).toInt
  val rightX = (Width * 1.5).toInt
  val topY = (Height * -0.5).toInt
  val bottomY = (Height * 1.5).toInt

  // The resolution, which will impact how many cols and rows are on the canvas
  val resolution = (Width * 0.01).toInt

  val numCols = (rightX - leftX) / resolution
  val numRows = (bottomY - topY) / resolution
  val grid = Array.ofDim[Double](numCols, numRows)

  override def settings(): Unit =
    size(Width, Height)

  override def draw(): Unit =
    background(255)

    for col &amp;lt;- 0 until numCols do
      for row &amp;lt;- 0 until numRows do
        val scaledX = col * 0.005
        val scaledY = row * 0.005
        // We switch this here to return the angle with perlin noise
        val angle = noise(scaledX.toFloat, scaledY.toFloat) * Pi * 2
        grid(col)(row) = angle

    val stepLength = 100
    val steps = 1000
    val lineCount = 2000

    (0 until lineCount).foreach: count =&amp;gt;
      drawCurve(steps, count, stepLength)

    noLoop()

  end draw

  def drawCurve(steps: Int, count: Int, stepLength: Int) =
    var x: Double = Random().between(0, rightX)
    var y: Double = Random().between(0, bottomY)

    (0 until steps).foreach: _ =&amp;gt;
      val xOffset: Double = x - leftX
      val yOffset: Double = y - topY

      val columnIndex: Int = (xOffset / resolution).toInt
      val rowIndex: Int = (yOffset / resolution).toInt

      if (columnIndex &amp;gt; 0 &amp;amp;&amp;amp; columnIndex &amp;lt; grid.length)
        &amp;amp;&amp;amp; (rowIndex &amp;gt; 0 &amp;amp;&amp;amp; rowIndex &amp;lt; grid(columnIndex).length)
      then
        val gridAngle: Double = grid(columnIndex)(rowIndex)

        val xStep: Double = stepLength * cos(gridAngle)
        val yStep: Double = stepLength * sin(gridAngle)

        val nextX = x + xStep
        val nextY = y + yStep

        line(x.toFloat, y.toFloat, nextX.toFloat, nextY.toFloat)

        x = nextX
        y = nextY

  end drawCurve
end Seven
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when ran, you'll start to see what we're actually going for.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/mvuXSUk.png&quot; alt=&quot;flow-fields seven&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Generated from &lt;a href=&quot;https://github.com/ckipp01/an-intro-to-flow-fields-in-scala/blob/main/Seven.scala&quot;&gt;&lt;code&gt;Seven.scala&lt;/code&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point you can really start playing around with the values of
&lt;code&gt;stepLength&lt;/code&gt;, &lt;code&gt;steps&lt;/code&gt;, and &lt;code&gt;lineCount&lt;/code&gt;, all which will pretty radically change
the way the generated image looks. It's also at this point that you can start
playing around with the way you generate your perlin noise. Chaning the &lt;code&gt;* Pi * 2&lt;/code&gt; will also start to radically change your image. Maybe you also want to start
changing the stroke width in your &lt;code&gt;drawCurve&lt;/code&gt; method, add some colors, etc.
Here's a couple examples of doing different things.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/FsqATKL.png&quot; alt=&quot;generative-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/gIaJYu4.png&quot; alt=&quot;generative-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/FRwfx06.png&quot; alt=&quot;generative-3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The possibilities with generative art are endless, and this is just a drop in
the bucket of techniques and ideas. My hope is that as I continue to explore
different facets of this field, you'll see them here. Again, I want to give more
focus to the art and blog by &lt;a href=&quot;https://tylerxhobbs.com/&quot;&gt;Tyler Hobbs&lt;/a&gt;, which is
truly inspirational. Hopefully these examples will give you what you need to
dive into flow fields and Scala. Until next time.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/an-intro-to-flow-fields-in-scala</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/an-intro-to-flow-fields-in-scala</guid><pubDate>Sun, 25 Jun 2023 00:00:00 +0200</pubDate></item><item><title>Lately - 03</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;What I've been up to lately.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Lately - 03&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;What I've been up to lately.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Lately - 03&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#lately&quot; id=&quot;lately&quot;&gt;Lately&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;March and April were pretty busy for me. Looking back, it was a whirlwind, but a
good one.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#working-at-the-scala-center&quot; id=&quot;working-at-the-scala-center&quot;&gt;Working at the Scala Center&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/scala-stairs.jpg&quot; alt=&quot;scala stairs&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the last months quite a bit of my time went into planning the last Scala
Tooling Summit that happened in Lausanne, Switzerland. I wrote about this on the
Scala site
&lt;a href=&quot;https://scala-lang.org/blog/2023/04/11/march-2023-scala-tooling-summit.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The summit was incredible for me. For the last few years I've spent so much time
working on tooling in the Scala ecosystem without ever really meeting many of
those I rub shoulders with online. It was incredible to be around so many that
have had such a large impact on the community, both past and present. It was one
of the highlights of my &amp;quot;scala&amp;quot; career.&lt;/p&gt;
&lt;p&gt;Some of the other work I've been focusing on is diving back into structured
diagnostics in Dotty and working towards actionable diagnostics. Here's a few
links related to this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/EYOre0BGv68&quot;&gt;The Journey of a Dotty Diagnostic&lt;/a&gt; - A talk I
gave at &lt;a href=&quot;https://scala.io/&quot;&gt;Scala.io&lt;/a&gt; about this in Paris.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/the-journey-of-a-dotty-diagnostic&quot;&gt;The written version of the talk&lt;/a&gt;
if that's more your thing&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lampepfl/dotty/issues/14904&quot;&gt;Large progress tracker for &lt;code&gt;DiagnosticCode&lt;/code&gt; and
&lt;code&gt;DiagnosticRelatedInformation&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lampepfl/dotty/issues/17337&quot;&gt;Large progress tracker for actionable diagnostics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://contributors.scala-lang.org/t/roadmap-for-actionable-diagnostics/6172&quot;&gt;Contributor discussion about actionable
diagnostics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the coming month I'll be heavily focusing on moving these forward.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#skan&quot; id=&quot;skan&quot;&gt;skan&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/skan-overview.png&quot; alt=&quot;overview of skan&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This last month I spent a chunk of time on writing
&lt;a href=&quot;https://github.com/ckipp01/skan&quot;&gt;skan&lt;/a&gt;, which is a minimal terminal kanban
board. I use it to track my ongoing work, and work I'd like to do. I wrote an
article about it &lt;a href=&quot;/blog/skan-and-building-your-own-tools&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#hardware&quot; id=&quot;hardware&quot;&gt;Hardware&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I &lt;em&gt;finally&lt;/em&gt; got my &lt;a href=&quot;https://github.com/rtitmuss/torn&quot;&gt;Torn V3&lt;/a&gt; build finished.
It took longer than I expected, I ruined one PCB in the process, and I learned a
ton. Overall a success. I still haven't nailed down the keymappings I want yet,
so I'm constantly re-flashing. That's why the cover isn't on the left hand side.
I also don't love my keys, so I plan on updating those and then taking a
non-potato quality photo. For now, enjoy this!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/finished-torn.jpg&quot; alt=&quot;finished keyboard&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#listening&quot; id=&quot;listening&quot;&gt;Listening&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some fantastic music was released in the last couple months. So these two
recommendations couldn't be further apart, but I've listened to both of these
albums a ton this last month.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://lanadelrey.lnk.to/oceanblvdWE&quot;&gt;Did you know there's a tunnel under Ocean
Blvd&lt;/a&gt; is wonderful. I wasn't a Lana Del
Rey fan in the past, but this album is one of my favorites this year so far.&lt;/p&gt;
&lt;p&gt;In a totally different vein I've also been listening to &lt;em&gt;Still Life In Decay&lt;/em&gt;
by FACS on repeat.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 100%; height: 120px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=428051111/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/artwork=small/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://wearefacs.bandcamp.com/album/still-life-in-decay&quot;&gt;Still Life In Decay by FACS&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;While not a new album, I recently came across Maxime Denuc and was blown away.
Specifically this album, Nachtorn. Here's a short blurb about it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nachthorn takes its name from one of the 78 stops that make up the main organ
in St. Antonius Church in Düsseldorf. This instrument, equipped with a system
developed by the German company Sinua, offers the possibility of controlling
all of its keyboards and timbres via a computer. The organ thus becomes a
powerful synthesiser. This set-up allowed me to fulfil an old dream of mine :
to create an entirely acoustic dance music piece with the organ as sole actor.
Oscillating between dub techno, harmonic locked grooves or after-hours pop,
Nachhorn proposes a hypnotic music piece whose lines sketch the outline of an
imaginary warehouse.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It's just so good.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 100%; height: 120px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=3065844444/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/artwork=small/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://maximedenuc.bandcamp.com/album/nachthorn&quot;&gt;Nachthorn by Maxime Denuc&lt;/a&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#watching&quot; id=&quot;watching&quot;&gt;Watching&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm probably late to the party here, but I've been watching and enjoying
&lt;a href=&quot;https://www.imdb.com/title/tt2707408/&quot;&gt;Narcos&lt;/a&gt; for the first time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/pablo-narcos.jpeg&quot; alt=&quot;pablo escobar in narcos&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the movie realm I watched &lt;a href=&quot;https://letterboxd.com/film/come-and-see/&quot;&gt;Come and
See&lt;/a&gt;, which was a roller coaster ride
of emotions. I didn't know much about it before watching, but it's not a film
I'll ever forget.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/come-and-see.jpeg&quot; alt=&quot;come and see film&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#traveling&quot; id=&quot;traveling&quot;&gt;Traveling&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since the previous &lt;a href=&quot;/blog/lately-02&quot;&gt;lately post&lt;/a&gt;, I've found myself in
Brussels, Newcastle, and Lausanne.&lt;/p&gt;
&lt;p&gt;There's a building right out of the Brussels Central Station that always catches
my eye.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/abstract-brussels.jpg&quot; alt=&quot;building in Brussels&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I was in Newcastle for work. Work is looking at expanding there, and I did some
presentations on our involvement in open source to a couple local universities.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/me-newcastle-university.jpg&quot; alt=&quot;me at newcastle&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The last night of the tooling summit we hiked up to a restaurant in Parc de
l'Hermitage for some Fondue. The route was full of hills and beautiful
buildings.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/lausanne-hills.jpg&quot; alt=&quot;hills in lausanne&quot; /&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/lately-03</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/lately-03</guid><pubDate>Mon, 1 May 2023 00:00:00 +0200</pubDate></item><item><title>skan and building your own tools</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;A short article on a recent tool I built for myself, and how I enjoyed the process.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;skan and building your own tools&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;A short article on a recent tool I built for myself, and how I enjoyed the process.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;skan and building your own tools&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#skan-and-building-your-own-tools&quot; id=&quot;skan-and-building-your-own-tools&quot;&gt;skan and building your own tools&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/images/skan-overview.png&quot; alt=&quot;overview of skan&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I've recently been spending some time building my own little tool to track my
TODOs, &lt;a href=&quot;https://github.com/ckipp01/skan&quot;&gt;skan&lt;/a&gt;. While building this, I've re-learned some lessons that I
learned early only in my programming journey and some even played a large role
in my decision to continue to pursue programming.&lt;/p&gt;
&lt;p&gt;The lessons are actually quite simple. They might not all be applicable to you,
and that's ok. We're all different, and we all learn differently. Hopefully
you'll still enjoy this, and maybe get some insight into the tools I used for
this project.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#theres-power-in-making-your-ideas-reality&quot; id=&quot;theres-power-in-making-your-ideas-reality&quot;&gt;There's power in making your ideas reality&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you first start programming (at least in more modern times) you undoubtedly
go through a phase where you're following tutorials, learning the basics of the
stack you're using, and just exploring what's possible. This is a great way
to get introduced to something, in fact I actually was in the process of doing
this with go right before I started this project. I was following a &lt;a href=&quot;https://www.youtube.com/playlist?list=PLLLtqOZfy0pcFoSIeGXO-SOaP9qLqd_H6&quot;&gt;video
series&lt;/a&gt;
on building a CLI kanban board with &lt;a href=&quot;https://github.com/charmbracelet/bubbletea&quot;&gt;Bubble
Tea&lt;/a&gt;, which is a really rad TUI
library for go. However, there comes a time where you &lt;em&gt;must&lt;/em&gt; move on from the
tutorials and start building your own ideas, and when you do that, magical
things happen. I've even found now that after you start building stuff on your
own, finishing tutorials gets pretty difficult because you (well, at least I do)
want to veer off immediately in your own direction and try your own things. The
reason for that, again at least for me, is that it's an incredible feeling to
build something from scratch -- to take an idea in your mind and make it a
reality. Early on when I was learning to program I was part of a community
called &lt;a href=&quot;https://merveilles.town/about&quot;&gt;Merveilles&lt;/a&gt; and they embody this idea,
that you can take your ideas and make them reality. Even if no one else cares,
no one else will use them, you care, and that's enough. Simply the act of
thinking of an idea and seeing it through fruition is a really powerful thing.
I've built lots of little tools early on that made me feel so engaged and
powerful when I was on my computer. Over the years I've gotten away from that,
focused on building tools for other people, and forgot how good it feels to
build something for myself. &lt;a href=&quot;https://github.com/ckipp01/skan&quot;&gt;skan&lt;/a&gt; has reminded me of how good it feels to
build something for me. Building this little tool has been probably the
most fun I've had programming in quite some time.&lt;/p&gt;
&lt;p&gt;If I was to summarize this, I'd say it's important to build your ideas, build
things just for you, and surround yourself with people that encourage you to do
it, even if it seems silly, because there's power in making your ideas reality.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#you-learn-more-when-things-dont-work&quot; id=&quot;you-learn-more-when-things-dont-work&quot;&gt;You learn more when things don't work&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'll be pretty honest, I'm not a JVM guru. Scala was the first language I truly
dove into and started to go beneath the surface, but I'm still in murky waters
when I go too deep. Since &lt;a href=&quot;https://github.com/ckipp01/skan&quot;&gt;skan&lt;/a&gt; is a CLI app, I didn't want to use the
JVM, which originally had me considering other languages. However, I was
intrigued when I saw &lt;a href=&quot;https://github.com/oyvindberg/tui-scala&quot;&gt;oyvindberg/tui-scala&lt;/a&gt;, especially since the
native image examples were quite snappy. &lt;a href=&quot;https://scala-cli.virtuslab.org/&quot;&gt;scala-cli&lt;/a&gt; also makes it
crazy simple to produce a native image with GraalVM, so I figured I'd give it a
try. My very limited experience in the past with native images have been pretty
terrible. Getting everything set up was a chore, and then when it was ready to
go you almost always hit on cryptic initialization errors that really do force
you to understand some stuff that I've never really cared about. I was
pleasantly surprised when I was able to just run &lt;code&gt;scala-cli package --native-image skan/&lt;/code&gt; and it produced me a nice little executable. That was
until I tried running it and got:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Exception in thread &amp;quot;main&amp;quot; java.lang.UnsatisfiedLinkError: Native library libcrossterm.dylib (/libcrossterm.dylib) cannot be found on the classpath.
        at tui.crossterm.NativeLoader.loadPackaged(NativeLoader.java:29)
        at tui.crossterm.NativeLoader.load(NativeLoader.java:11)
        at tui.crossterm.CrosstermJni.&amp;lt;clinit&amp;gt;(CrosstermJni.java:9)
        at tui.withTerminal$.apply(withTerminal.scala:6)
        at skan.project$package$.run(project.scala:248)
        at skan.run.main(project.scala:18)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thankfully, the author of &lt;a href=&quot;https://github.com/oyvindberg/tui-scala&quot;&gt;tui-scala&lt;/a&gt; already had some of this
documented and was willing to give a helping hand. Now I have this lovely
&lt;a href=&quot;https://github.com/ckipp01/skan/blob/bc6f583ba598335c41a1a4431835c2b0ecea2e4e/scripts/package-setup.scala&quot;&gt;hack&lt;/a&gt;
that essentially does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Uses coursier to fetch a jar that includes some files I need (necessary likely due to &lt;a href=&quot;https://github.com/oracle/graal/issues/5219&quot;&gt;this&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Unpacks the jar, and based on the OS, grabs the necessary files and copies
them to my resources folder.&lt;/li&gt;
&lt;li&gt;Then when I package the app I include &lt;code&gt;--graalvm-args -H:IncludeResources=libcrossterm.dylib&lt;/code&gt; as a flag.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Again, it's hacky, but it works. My command to actual make the image on MacOS
looks like this (inside of a Makefile):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package-mac:
	make prepare-for-graal
	make generate-build-info
	scala-cli --power \
		package \
		--native-image \
		--graalvm-java-version 19 \
		--graalvm-version 22.3.1 \
		--graalvm-args --verbose \
		--graalvm-args --no-fallback \
		--graalvm-args -H:+ReportExceptionStackTraces \
		--graalvm-args --initialize-at-build-time=scala.runtime.Statics$$VM \
		--graalvm-args --initialize-at-build-time=scala.Symbol \
		--graalvm-args --initialize-at-build-time=scala.Symbol$$ \
		--graalvm-args --native-image-info \
		--graalvm-args -H:IncludeResources=libcrossterm.dylib \
		--graalvm-args -H:-UseServiceLoaderFeature \
		skan/ -o out/skan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;make prepare-for-graal&lt;/code&gt; call before the rest of the stuff from
packaging. That's what handles the setup. Again, much of this is copied from
&lt;a href=&quot;https://github.com/oyvindberg/tui-scala&quot;&gt;tui-scala&lt;/a&gt;, but because it didn't work at first it forced me to
actually look into the flags, better understand them, better understand what
&lt;a href=&quot;https://github.com/crossterm-rs/crossterm&quot;&gt;crossterm&lt;/a&gt; even is, and better
understand the inner-workings of &lt;a href=&quot;https://github.com/oyvindberg/tui-scala&quot;&gt;tui-scala&lt;/a&gt;. Arguably, I wouldn't
have learned any of this if it all &lt;em&gt;just worked&lt;/em&gt;. Don't misunderstand me, I do
wish it all &lt;em&gt;just worked&lt;/em&gt;, but I also know that in software development many
times things won't. The skills you acquire when they don't and the knowledge you
gain while trying to understand &lt;em&gt;why&lt;/em&gt; something doesn't work is valuable.
Therefore, I think you learn more when things don't work.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#you-learn-to-appreciate-things-when-you-dont-have-them-anymore&quot; id=&quot;you-learn-to-appreciate-things-when-you-dont-have-them-anymore&quot;&gt;You learn to appreciate things when you don't have them anymore&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you have keen eyes you may have noticed another call up above, &lt;code&gt;make generate-build-info&lt;/code&gt;. This sort of relates to the above, but when things don't
work, you have to get creative. I'll be honest, I don't often think about tools
like &lt;a href=&quot;https://github.com/sbt/sbt-buildinfo&quot;&gt;sbt-buildinfo&lt;/a&gt;. I just use them and
move on with my day. The whole idea of source generators can just be glossed
over because they do what you want them to do, generate stuff so you can keep
coding.&lt;/p&gt;
&lt;p&gt;Since &lt;a href=&quot;https://github.com/ckipp01/skan&quot;&gt;skan&lt;/a&gt; is a CLI app, you want a way to display the current version
that is being used. The way I would typically do that is with a tool like I
mentioned above, &lt;a href=&quot;https://github.com/sbt/sbt-buildinfo&quot;&gt;sbt-buildinfo&lt;/a&gt;. It's common to use, computes
the version from git, and generates a &lt;code&gt;BuildInfo.scala&lt;/code&gt; file that you can use.
&lt;a href=&quot;https://scala-cli.virtuslab.org/&quot;&gt;scala-cli&lt;/a&gt; has no ability to &lt;a href=&quot;https://github.com/VirtusLab/scala-cli/issues/610&quot;&gt;generate code like
this&lt;/a&gt;. It's not until you
realize you can't do something or that something isn't supported that you truly
miss the ability to do that thing. To get around this I have another script that
ends up being ran before any compilation is ran with scala-cli. The script is
quite simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;//&amp;gt; using scala &amp;quot;3.3.0-RC4&amp;quot;
//&amp;gt; using options &amp;quot;-deprecation&amp;quot;, &amp;quot;-feature&amp;quot;, &amp;quot;-explain&amp;quot;, &amp;quot;-Wunused:all&amp;quot;
//&amp;gt; using lib &amp;quot;com.lihaoyi::os-lib:0.9.1&amp;quot;
//&amp;gt; using lib &amp;quot;com.outr::scribe:3.11.1&amp;quot;
//&amp;gt; using lib &amp;quot;com.github.sbt::dynver:5.0.0&amp;quot;

package skan.scripts

import java.util.Date

import sbtdynver.DynVer

@main def run() =
  val target = os.pwd / &amp;quot;skan&amp;quot; / &amp;quot;.scala-build&amp;quot; / &amp;quot;BuildInfo.scala&amp;quot;
  val version = DynVer.version(Date())
  scribe.info(s&amp;quot;Current version is ${version}&amp;quot;)
  scribe.info(s&amp;quot;Generating BuildInfo.scala into ${target}&amp;quot;)
  val buildInfo = s&amp;quot;&amp;quot;&amp;quot;|package skan
                      |
                      |object BuildInfo:
                      |  val version = &amp;quot;${version}&amp;quot;
                      |&amp;quot;&amp;quot;&amp;quot;.stripMargin
  os.write.over(target = target, data = buildInfo, createFolders = true)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I uses the library portion of &lt;a href=&quot;https://github.com/sbt/sbt-dynver&quot;&gt;sbt-dynver&lt;/a&gt; to
compute the version based off git tags, generates a &lt;code&gt;BuildInfo.scala&lt;/code&gt; file with
that version, and then copies that file into the main &lt;code&gt;skan&lt;/code&gt; directory under the
&lt;code&gt;.scala-build/&lt;/code&gt; directory that scala-cli creates. Then inside of my
&lt;code&gt;project.scala&lt;/code&gt; I have the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;//&amp;gt; using file &amp;quot;../.scala-build/BuildInfo.scala&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line just ensures that the generated file is &amp;quot;sourced&amp;quot; and can be used just
like another file in my package.&lt;/p&gt;
&lt;p&gt;You're probably thinking, this is brittle, hacky, and ugly. My response would be
yes, yes it is. The lesson here is that we often overlook simple tools that
cause us to not have to use hacks like this to accomplish things that other
tools may easily offer you. The &lt;code&gt;BuildInfo&lt;/code&gt; example is just a small one, there
are other source generators that are much more involved and difficult to mimic,
especially with 10 lines of code.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#build-your-own-tools-or-dont&quot; id=&quot;build-your-own-tools-or-dont&quot;&gt;Build your own tools, or don't&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I've said it multiple times, but &lt;a href=&quot;https://github.com/ckipp01/skan&quot;&gt;skan&lt;/a&gt; was such a fun little project for
me. It's not the most challenging software, most elegant, or even unique. There
are other TUI kanban boards out there with a way larger feature set, but that's
sort of the beauty of software development -- you can build whatever you want to
fit your exact needs. I encourage you to build something for yourself. Whether
it makes sense for other people, it doesn't matter. You'll probably hit on
things that don't work and learn something new in the process. You'll probably
wish you had something that X other tool had, and it'll cause you to add some
hack that causes you to appreciate that other tool. At the end, you can look
back at this tool you built for yourself and feel proud that you built something
from scratch, and it's yours. The same concept probably applies to libraries,
but I despise writing those. Maybe you despise the idea of writing your own
tool, and that's ok too.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/skan-and-building-your-own-tools</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/skan-and-building-your-own-tools</guid><pubDate>Thu, 27 Apr 2023 00:00:00 +0200</pubDate></item><item><title>Lately - 02</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;What I've been up to lately.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Lately - 02&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;What I've been up to lately.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Lately - 02&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#lately&quot; id=&quot;lately&quot;&gt;Lately&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A lot of this past month was prepping for this next month. March will be a pretty
busy month for me with traveling for work and a lot of events happening.
Sometimes months like this are difficult for me since you look back at the work
you've done and feel like you didn't accomplish much, until all that prep-work
flourishes in the future.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#working-at-the-scala-center&quot; id=&quot;working-at-the-scala-center&quot;&gt;Working at the Scala Center&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As many of you may know, &lt;a href=&quot;https://lunatech.com&quot;&gt;work&lt;/a&gt; has lent me out to the
&lt;a href=&quot;https://scala.epfl.ch/&quot;&gt;Scala Center&lt;/a&gt; and this last month was a lot of
maintenance work around the ecosystem. The more I work in Scala tooling the more
I recognize just &lt;em&gt;how small&lt;/em&gt; that community of people are. The amount of work
that the Scala Center gets done in pure maintenance alone across a handful of
tools is miraculous.&lt;/p&gt;
&lt;p&gt;Apart from maintenance work a large chunk of my time has been spent in
preparation for a Scala Tooling Summit that will be happening in March. I look
forward to give an update afterwards, but around 30 people from important tools
in the ecosystem, the compiler team, and community contributors are all coming
together to discuss the state of tooling, look at collective problems, and
attempt to cement a clear roadmap moving forward focused on de-duplication of
efforts between teams, unified approaches, and stabilisation. This has been an
ongoing effort behind the scenes for the past few months. I'm quite excited
about this, and expect to hear more after the meet.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#hardware&quot; id=&quot;hardware&quot;&gt;Hardware&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In part it could be because I'm now working on tooling full-time during the day,
but also probably more attributed to a bit of burn out, but I've found myself
coding much less in my free time and instead looking into new interests, or
spending time re-exploring past ones. I don't have a traditional computer
science or engineering degree like many of my colleagues seem to have, so much
of the hardware / low-level things you'd explore in college I've never had the
opportunity to. Lately for me that has manifested in my exploring circuitry,
learning how to solder, and doing a few small projects. One project that I'm
excited about is a &lt;a href=&quot;https://github.com/rtitmuss/torn&quot;&gt;Torn V3 build&lt;/a&gt; that I've
started:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/torn-layout.jpeg&quot; alt=&quot;torn v3 build layout&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can follow my progress on this on &lt;a href=&quot;https://hachyderm.io/@ckipp/109937778576136195&quot;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#reading&quot; id=&quot;reading&quot;&gt;Reading&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Related to the above, I stumbled upon &lt;a href=&quot;https://www.goodreads.com/book/show/44882.Code&quot;&gt;&lt;em&gt;Code: The Hidden Language of Computer
Hardware and Software&lt;/em&gt;&lt;/a&gt; by
Charles Petzold, which is a fantastic overview of how code was born. Starting
with an wide overview of braille, logic gates, and binary calculators you build
you way up to a modern computer. It's a fascinating read that has been teaching
me a lot of things I've never dove into before. It's been a great supplement for
me as I've started diving into the hardware side of things to better understand
what I'm actually doing.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#software&quot; id=&quot;software&quot;&gt;Software&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A crazy amount of my time is spent on my computer, especially in my terminal. So
my software choices in this area aren't made lightly, and I often don't switch.
However this past month has me exploring a few new pieces of software, a couple
which seem to be sticking.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://arc.net/&quot;&gt;Arc&lt;/a&gt; is one of the first browsers that I've tried that really
seem to challenge the way I use a browser. Challenging the way you use tabs,
shortcuts, and layout. At first it just seemed silly, but the more I've gotten
used to it and have been learning the shortcuts the more I'm convinced that
there's been barely any advancement in the way we use browsers since &lt;em&gt;almost&lt;/em&gt; as
long as I can remember. While I'm for sure not using Arc to its fullest I'm
enjoying slowing getting accustomed to it, and I will likely stick with it.&lt;/p&gt;
&lt;p&gt;I've been a long-time &lt;a href=&quot;https://iterm2.com/&quot;&gt;iTerm2&lt;/a&gt; user. Even though I barely
use most of it's features, especially because I use
&lt;a href=&quot;https://github.com/tmux/tmux&quot;&gt;tmux&lt;/a&gt; as a terminal multiplexer, I've valued the
stability it offered and the ability to just display things the way I've wanted.
However &lt;a href=&quot;https://wezfurlong.org/wezterm/&quot;&gt;wezterm&lt;/a&gt; offers much of the same
stability, is super snappy, and is configured via Lua. I've recently made the
jump and I'll definitely be sticking with it as the easy configuration that can
be stored in my dots repo along with nice features like instant reloading are
just great.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#listening&quot; id=&quot;listening&quot;&gt;Listening&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This month I stumbled on a &lt;em&gt;new to me&lt;/em&gt; band called &lt;a href=&quot;https://hermanosgutierrez.bandcamp.com/&quot;&gt;Hermanos
Gutiérrez&lt;/a&gt; that have been on repeat.
Such a chill vibe that's easy to listen to while working, relaxing, or
commuting.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 100%; height: 120px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=126397725/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/artwork=small/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://hermanosgutierrez.bandcamp.com/album/el-bueno-y-el-malo&quot;&gt;El Bueno Y El Malo by Hermanos Gutiérrez&lt;/a&gt;&lt;/iframe
&lt;p&gt;They did an interesting &lt;a href=&quot;https://youtu.be/wTC3i19hCS0&quot;&gt;interview&lt;/a&gt; where they
spoke about some of their inspirations for their previous record and they listed
some other fantastic artists like Adrian Quesada. He did a remake of &lt;em&gt;El Muchacho
De Los Ojos Tristes&lt;/em&gt; with Tita that has also been on repat for me this month.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 100%; height: 120px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=35855793/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/artwork=small/track=3648796777/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://adrianquesada.bandcamp.com/album/boleros-psicod-licos&quot;&gt;Boleros Psicodélicos by Adrian Quesada&lt;/a&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#watching&quot; id=&quot;watching&quot;&gt;Watching&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A movie I watched this past month that I really enjoyed was &lt;a href=&quot;https://en.wikipedia.org/wiki/Bullet_Train_(film)&quot;&gt;Bullet
Train&lt;/a&gt;, which is a film
adaption of a &lt;a href=&quot;https://en.wikipedia.org/wiki/Bullet_Train_(novel)&quot;&gt;Japanese Book titled
マリアビートル&lt;/a&gt;. Such an
easy film to watch, some dark humour, and sprinkled with ridiculousness. Can we
also take a second to appreciate the outfit Brad Pitt was wearing, which is one
I'd happily wear.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/bullet-train-brad-pitt.jpeg&quot; alt=&quot;Brad Pitt outfit&quot; /&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/lately-02</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/lately-02</guid><pubDate>Sat, 4 Mar 2023 00:00:00 +0100</pubDate></item><item><title>Lately - 01</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;What I've been up to lately.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Lately - 01&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;What I've been up to lately.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Lately - 01&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#lately&quot; id=&quot;lately&quot;&gt;Lately&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I follow a few blogs that are in the habit of doing regular updates about what
they are up to on a regular basis. A great example of this is the &lt;em&gt;Recently&lt;/em&gt;
posts by &lt;a href=&quot;https://macwright.com/&quot;&gt;Tom MacWright&lt;/a&gt;. His blog is great by the way,
check it out. In this new year I wanted to get in the habit of writing more
frequently and sharing more of my interests than just programming. So you're
likely to see one of these each month. It's also fitting as I'm normally pretty
spread around with what I work on, so this allows me to simply share what I've
been up to without needing to really dive deep into a topic to write about. I
hope you enjoy.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#scala-center&quot; id=&quot;scala-center&quot;&gt;Scala Center&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recently &lt;a href=&quot;https://lunatech.com/&quot;&gt;work&lt;/a&gt; has lent me out to the &lt;a href=&quot;https://scala.epfl.ch/&quot;&gt;Scala
Center&lt;/a&gt; to do some work on tooling in the Scala
ecosystem. If you're not familiar, the Scala Center is a non-profit focused
on Scala education and maintenance of some of the core parts of the Scala
compiler and tooling ecosystem. Some of the first things I've dove into first
when joining have been &lt;a href=&quot;https://build-server-protocol.github.io/&quot;&gt;BSP&lt;/a&gt; related.&lt;/p&gt;
&lt;p&gt;Firstly in an attempt to simplify the
&lt;a href=&quot;https://scalacenter.github.io/bloop/&quot;&gt;Bloop&lt;/a&gt; build, I started basically hacking
away and removing things. Bloop is a build server for Scala/Java that focuses on
speed. It's incredibly fast and has been pivotal to get Metals where it is now,
but also suffered from an equally incredibly confusing build. I was able to rip
out the Maven plugin, Gradle plugin, and the core config library into their own
repositories to be developed and versions standalone. Bloop also had a complex
meta-meta build that made contributing and getting started with Bloop difficult.
That's all now gone. If you're curious, you can find some of this work in the
following places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/scalacenter/bloop-config&quot;&gt;new home&lt;/a&gt; of &lt;code&gt;bloop-config&lt;/code&gt;
and the &lt;a href=&quot;https://github.com/scalacenter/bloop/pull/1868&quot;&gt;pr&lt;/a&gt; removing it from
Bloop.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/scalacenter/bloop-maven-plugin&quot;&gt;new home&lt;/a&gt; of the
&lt;code&gt;bloop-maven-plugin&lt;/code&gt; and the
&lt;a href=&quot;https://github.com/scalacenter/bloop/pull/1948&quot;&gt;pr&lt;/a&gt; removing it from Bloop.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/scalacenter/gradle-bloop&quot;&gt;new home&lt;/a&gt; of the
&lt;code&gt;gradle-bloop&lt;/code&gt; plugin and the
&lt;a href=&quot;https://github.com/scalacenter/bloop/pull/1951&quot;&gt;pr&lt;/a&gt; removing it.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/scalacenter/bloop/pull/1956&quot;&gt;pr&lt;/a&gt; re-working and
removing the meta-meta build in Bloop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apart from working on Bloop my main focus has been looking into the default
choice in &lt;a href=&quot;https://scalameta.org/metals/&quot;&gt;Metals&lt;/a&gt;, the Scala language server, to
use Bloop as your build server. This last year more and more users have started
defaulting to using the built-in BSP support in either
&lt;a href=&quot;https://www.scala-sbt.org/&quot;&gt;sbt&lt;/a&gt; or
&lt;a href=&quot;https://com-lihaoyi.github.io/mill/mill/Intro_to_Mill.html&quot;&gt;Mill&lt;/a&gt;. Due to this
I've written up a report on the differences between the various build servers
&lt;a href=&quot;https://github.com/scalameta/metals/discussions/4505#discussioncomment-4571546&quot;&gt;here&lt;/a&gt;
that give a pretty good overview and a plan to move forward. I've also then been
working on small things related to this task in preparation to this move.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#neovim&quot; id=&quot;neovim&quot;&gt;Neovim&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This last month I did a handful of small quality of life improvements and
updates to &lt;a href=&quot;https://github.com/scalameta/nvim-metals&quot;&gt;nvim-metals&lt;/a&gt; and have seen
a bunch of newcomers in the new year. Must be that learning Vim was a new years
resolution for a handful of Scala users!&lt;/p&gt;
&lt;p&gt;More excitingly after a &lt;em&gt;long&lt;/em&gt; time of inactivity on the &lt;a href=&quot;https://github.com/tree-sitter/tree-sitter-scala&quot;&gt;Scala Treesitter
grammar&lt;/a&gt;, a group of us from
the Scala Community were given maintainer access and started hammering out
improvements, especially to the Scala 3 grammar. Huge shout-out to
&lt;a href=&quot;https://types.pl/@eed3si9n&quot;&gt;Eugene&lt;/a&gt; and
&lt;a href=&quot;https://twitter.com/velvetbaldmime&quot;&gt;Anton&lt;/a&gt; for tackling the majority of the
Scala 3 improvements.&lt;/p&gt;
&lt;p&gt;Just check out the difference in syntax highlighting before and after the last
month. Some of them are obvious, but some are more subtle. Overall, it's a huge
improvement.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Before&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/ts-before.png&quot; alt=&quot;tree-sitter-scala before&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;After&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/ts-after.png&quot; alt=&quot;tree-sitter-scala after&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#this-site&quot; id=&quot;this-site&quot;&gt;This Site&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Over the years this site has taken all sorts of forms. However, the focus has
always been on a minimal self-made site. I really enjoy making my own site from
scratch, and I enjoy looking at other sites that have done the same. I'll
probably do a whole post on this in the future when I feel a bit happier with
how everything is working together, but for now I'll outline a few changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I migrated this site from using &lt;a href=&quot;https://pandoc.org/&quot;&gt;Pandoc&lt;/a&gt; via a bash
script to a small &lt;a href=&quot;https://scala-cli.virtuslab.org/&quot;&gt;scala-cli&lt;/a&gt; project with
Scala 3 using &lt;a href=&quot;https://github.com/vsch/flexmark-java&quot;&gt;flexmark&lt;/a&gt; to turn my
markdown into html.&lt;/li&gt;
&lt;li&gt;I now have an RSS feed that you can follow &lt;a href=&quot;../rss.xml&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I've started to create &lt;a href=&quot;../lists&quot;&gt;lists&lt;/a&gt; to better keep track of things that
I'd like to return to. I've always sort of done this, but I'd like to start
doing it in one place and making it public. For now they're each a yaml file
which gets read up and then turned into html via
&lt;a href=&quot;https://github.com/circe/circe-yaml&quot;&gt;circe-yaml&lt;/a&gt; and
&lt;a href=&quot;https://github.com/com-lihaoyi/scalatags&quot;&gt;Scalatags&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Apart from syntax highlighting and &lt;a href=&quot;https://pirsch.io/&quot;&gt;Pirsch analytics&lt;/a&gt;
there is no JS necessary for my site.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#listening&quot; id=&quot;listening&quot;&gt;Listening&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Firstly, this year I'm doing a little experiment where I track all the new
albums that I listen to fully and enjoy. You can see my progress on this under
&lt;a href=&quot;../lists/albums&quot;&gt;/lists/albums&lt;/a&gt;. This is an attempt to be a bit more attentive to the
new music I'm discovering and also a fun way to track my favorites of the year.
I'm excited to see what it looks like at the end of the year.&lt;/p&gt;
&lt;p&gt;There's been two albums that have really stood out to me this month and have
been on repeat.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/portrait-of-a-dog.jpeg&quot; alt=&quot;portrait of a day&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://jonahyano.bandcamp.com/album/portrait-of-a-dog&quot;&gt;Jonah Yano's&lt;/a&gt; new album
&lt;em&gt;portrait of a dog&lt;/em&gt; is a funky smooth project done with
&lt;a href=&quot;https://tv.badbadnotgood.com/&quot;&gt;BADBADNOTGOOD&lt;/a&gt;. I love the crisp minimal drum
lines, driving keys, and Jonah's magical voice. Often when listening I just get
lost in it, my eyes close, and I just enjoy it. The whole album is great.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/gigis-recovery.jpeg&quot; alt=&quot;Gigi's Recovery&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Funny enough, when I first started listening to this album I didn't really dig
it. It took me until I was around half way through, and then I fell in love.
&lt;em&gt;Gigi's Recovery&lt;/em&gt; by &lt;a href=&quot;https://themurdercapital.bandcamp.com/album/gigis-recovery&quot;&gt;The Murder
Capital&lt;/a&gt; is the
first album of theirs that I listened to, but I'll definitely be following them
in the future. I get some &lt;a href=&quot;https://protomartyrband.com/&quot;&gt;Protomartyr&lt;/a&gt; vibes from
them. They've got great lyrics, really catchy breaks, and throughout the album
they know when to go hard and when to just keep it simple. By far my two
favorites on the album are &lt;em&gt;The Lie Becomes the Self&lt;/em&gt; and &lt;em&gt;We Had to Disappear&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#reading&quot; id=&quot;reading&quot;&gt;Reading&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This last month I read &lt;a href=&quot;https://www.goodreads.com/book/show/251651.The_Way_of_Life_According_to_Lao_Tzu&quot;&gt;The Way of Life, According to Lao
Tzu&lt;/a&gt;.
It's been a book I've wanted to read for some time, and I felt like it was maybe
a good one to start the year off with. I read it slowly, just a few of the poems
per day. While I enjoyed t, the book wasn't as impactful as I was expecting
for some reason. I'll simply leave it as that.&lt;/p&gt;
&lt;p&gt;I've also spent some time catching up on the articles of the sites I follow via
RSS. You can find a handful of them &lt;a href=&quot;../lists/sites&quot;&gt;here&lt;/a&gt;. Another article I
often revisit that I did this month was the great article by Erin 'Folletto'
Casali, &lt;a href=&quot;https://intenseminimalism.com/2020/the-impact-of-toxic-influencers-on-communities/&quot;&gt;The Impact of Toxic Influencers on
Communities&lt;/a&gt;.
If you're a part of the Scala community, I highly recommend the read in light of
recent events and personalities.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#traveling&quot; id=&quot;traveling&quot;&gt;Traveling&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This month I spent a bit of time in Paris for work giving a talk called &lt;a href=&quot;../slides/open-source&quot;&gt;What
I've learned in open source and why I think you should
contribute.&lt;/a&gt;. It was for a semi-regularly hackday that we have
at work where colleagues come together to give some talks, do workshops, and
just hack on things together.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/paris-chris.png&quot; alt=&quot;me in front of boot care&quot; /&gt;&lt;/p&gt;
&lt;p&gt;_Me in front of &lt;a href=&quot;https://www.instagram.com/bootcafe/&quot;&gt;Boot Cafe&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/paris-wall.png&quot; alt=&quot;wall in paris with a face on it&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Just a wall in Le Marais.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../images/paris-rodin.png&quot; alt=&quot;wall statue in the Rodin Museum&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Statue on the wall in the &lt;a href=&quot;https://www.musee-rodin.fr/en&quot;&gt;Rodin Museum&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/lately-01</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/lately-01</guid><pubDate>Mon, 23 Jan 2023 00:00:00 +0100</pubDate></item><item><title>My Top Albums of 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;These are some of my favorite albums of 2022&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/tana-talk-4.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;My Top Albums of 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/tana-talk-4.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;These are some of my favorite albums of 2022&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;My Top Albums of 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#my-top-albums-of-2022&quot; id=&quot;my-top-albums-of-2022&quot;&gt;My Top albums of 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;One of my goals this past year was to be a bit more attentive to the music I
listen to. In the past I listened to so much music passively, partly due to me
listening while programming. I used to be way more into classical music when I
was younger and a good friend and I used to listen to an album a month closely.
We would jot down notes and make sure we just sat and listened through the full
album end to end. I learned so much in that exercise. I realized I had really
gotten away from listening to music in that manner so this past year I wanted to
be a bit more attentive. I'm happy to report that I feel like I was way more
attentive, and I loved it. Even doing the simple &lt;em&gt;Music of the Month&lt;/em&gt; on here
was a ton of fun. So many people messaged me, engaged on Twitter, etc. It made
it that much sweeter. Enjoying music with other people is just the best.&lt;/p&gt;
&lt;p&gt;On that note, I wanted to conclude the year not with a &lt;em&gt;December Music of the
Month&lt;/em&gt; post, but instead share a collection of new albums that I really enjoyed
this past year for different reasons and at different times. I won't offer
in-depth reviews on them or really any thoughts at all. But I hope you enjoy
some of these albums as much as I did. If Spotify is your thing, feel free to
listen through the list
&lt;a href=&quot;https://open.spotify.com/playlist/2VBs5RVNfNVGwGaFOkwRfd?si=a2ad64fd30844b8d&quot;&gt;here&lt;/a&gt;.
I also encourage you to support your favorite artists and buy their music
directly.&lt;/p&gt;
&lt;p&gt;Here we go, in no particular order.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#benny-the-butcher---tana-talk-4&quot; id=&quot;benny-the-butcher---tana-talk-4&quot;&gt;Benny The Butcher - Tana Talk 4&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/tana-talk-4.png&quot; alt=&quot;tana talk 4 album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bennythebutcher.ffm.to/tanatalk4&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://genius.com/albums/Benny-the-butcher/Tana-talk-4&quot;&gt;Genius Lyrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bennythebutcherstore.com/&quot;&gt;Benny The Butcher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pitchfork.com/reviews/albums/benny-the-butcher-tana-talk-4/&quot;&gt;Pitchfork review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=rDE9o0VRWRo&quot;&gt;theneedledrop review video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#jid---the-forever-story&quot; id=&quot;jid---the-forever-story&quot;&gt;JID - The Forever Story&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/the-forever-story.jpeg&quot; alt=&quot;the forever story album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jid.lnk.to/TheForeverStory&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://genius.com/albums/Jid/The-forever-story&quot;&gt;Genius Lyrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jidsv.com/#/&quot;&gt;JID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pitchfork.com/reviews/albums/jid-the-forever-story/&quot;&gt;Pitchfork review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=zOm5n0OJLfc&quot;&gt;theneedledrop review video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#kenny-beats---louie&quot; id=&quot;kenny-beats---louie&quot;&gt;Kenny Beats - LOUIE&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/louie.jpeg&quot; alt=&quot;LOUIE album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kennybeats.bandcamp.com/album/louie&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pitchfork.com/reviews/albums/kenny-beats-louie/&quot;&gt;Genius Lyrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Kenny_Beats&quot;&gt;Kenny Beats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pitchfork.com/reviews/albums/kenny-beats-louie/&quot;&gt;Pitchfork review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6YoUjUGvHUk&quot;&gt;theneedledrop review video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#messa---close&quot; id=&quot;messa---close&quot;&gt;Messa - Close&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/close.jpeg&quot; alt=&quot;close album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://messaproject.bandcamp.com/album/close&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://linktr.ee/messaband&quot;&gt;Messa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#moncho-y-su-banda---que-bellas-son&quot; id=&quot;moncho-y-su-banda---que-bellas-son&quot;&gt;Moncho y Su Banda - Que Bellas Son&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/que-bellas-son.jpeg&quot; alt=&quot;que bellas son album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://monchoysubanda.bandcamp.com/album/que-bellas-son&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#tomppabeats---habbits&quot; id=&quot;tomppabeats---habbits&quot;&gt;Tomppabeats - Habbits&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/habbits.jpeg&quot; alt=&quot;habbits album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tomppabeats.bandcamp.com/album/habits&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.instagram.com/tomppabeats/?hl=en&quot;&gt;Tomppabeats&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#vega-trails---tremors-in-the-static&quot; id=&quot;vega-trails---tremors-in-the-static&quot;&gt;Vega Trails - Tremors in the Static&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/tremors-in-the-static.jpeg&quot; alt=&quot;tremors in the static album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vegatrails.bandcamp.com/album/tremors-in-the-static&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vegatrails.com/&quot;&gt;Vega Trails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#wilma-vritra---grotto&quot; id=&quot;wilma-vritra---grotto&quot;&gt;Wilma Vritra - Grotto&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/grotto.jpeg&quot; alt=&quot;grotto album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wilmavritra.bandcamp.com/album/grotto&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://genius.com/albums/Wilma-vritra/Grotto&quot;&gt;Genius Lyrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.wilmavritra.com/&quot;&gt;Wilma Vritra&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#yaya-bey---remember-your-north-star&quot; id=&quot;yaya-bey---remember-your-north-star&quot;&gt;Yaya Bey - Remember Your North Star&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/remember-your-north-star.jpeg&quot; alt=&quot;remember your north star album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://yayabey.bandcamp.com/album/remember-your-north-star&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://genius.com/albums/Yaya-bey/Remember-your-north-star&quot;&gt;Genius Lyrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yaya-bey.com/&quot;&gt;Yaya Bey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#zack-fox---wood-tip-ep&quot; id=&quot;zack-fox---wood-tip-ep&quot;&gt;Zack Fox - wood tip EP&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../images/albums/wood-tip.jpeg&quot; alt=&quot;wood tip album cover&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://soundcloud.com/corduroycondom/sets/wood-tip&quot;&gt;Listen Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://genius.com/albums/Zack-fox/Wood-tip-ep&quot;&gt;Genius Lyrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.google.com/spreadsheets/d/1Wx1Mi0iex-dWrQctVCMzYxIbKb8Y0WFSjfyDB3aAMFc/edit?usp=sharing&quot;&gt;Zack Fox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/my-top-albums-of-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/my-top-albums-of-2022</guid><pubDate>Wed, 28 Dec 2022 00:00:00 +0100</pubDate></item><item><title>Music of the Month - November 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - November 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - November 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---november-2022&quot; id=&quot;music-of-the-month---november-2022&quot;&gt;Music of the Month - November 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;November. November just flew by. It was here, and now it's gone already. This
last month was a huge focus month for me with lots going on. Typically when I am
in focus mode I'm headphones on, head down. This was one of those months. I was
all over the place again with music, but here was an album and a song that stuck
out to me this month.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So this album was one that's been on my radar for a while, but I haven't really
listened through it much. This month it just stuck for me. The album of the
month for me was &lt;em&gt;LOUIE&lt;/em&gt; by Kenny Beats. Killer album. It's his first solo
project and the mans got skills to go around. Really well produced, dope beats,
unique, and just pleasant to listen to. I listened to it while work, while
commuting, and even hanging with friends. It just fits with whatever you're
doing.&lt;/p&gt;
&lt;iframe max-width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/M1v5fvBP5TM&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The song of the month is in part because I was listening to Kenny Beats so much.
Typically when I get stuck on someone for a while I branch out to other projects
they've worked on or artists they've worked with. One of the many incredibly
artists that Kenny has worked with has been Denzel Curry. On his album &lt;em&gt;Melt My
Eyez See Your Future&lt;/em&gt; he's got this song called &lt;em&gt;Walkin&lt;/em&gt; which just gets in your
head and stays there. It's the song of the month for me this last month.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 350px; height: 470px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=2576649463/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/track=3208063144/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://denzelcurrymusic.bandcamp.com/album/melt-my-eyez-see-your-future&quot;&gt;Melt My Eyez See Your Future by Denzel Curry&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-november-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-november-2022</guid><pubDate>Thu, 1 Dec 2022 00:00:00 +0100</pubDate></item><item><title>The Journey of a Dotty Diagnostic</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;A dive into the world of Diagnostics in Scala 3&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;The Journey of a Dotty Diagnostic&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;A dive into the world of Diagnostics in Scala 3&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;The Journey of a Dotty Diagnostic&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#the-journey-of-a-dotty-diagnostic&quot; id=&quot;the-journey-of-a-dotty-diagnostic&quot;&gt;The Journey of a Dotty Diagnostic&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I was inspired by seeing the companion article &lt;a href=&quot;https://nrinaudo.github.io/articles/function_reuse.html&quot;&gt;&lt;em&gt;Function reuse is just wishful
thinking&lt;/em&gt;&lt;/a&gt; for &lt;a href=&quot;https://twitter.com/nicolasrinaudo&quot;&gt;Nicolas Rinaudo's&lt;/a&gt; talk, so I
decided to do the same. This is meant to be a companion article for my talk,
&lt;a href=&quot;https://www.chris-kipp.io/slides/dotty-diagnostics&quot;&gt;&lt;em&gt;The Journey of a Dotty Diagnostic&lt;/em&gt;&lt;/a&gt; given at
&lt;a href=&quot;https://scala.io/&quot;&gt;ScalaIO&lt;/a&gt; 2022 in Paris.&lt;/p&gt;
&lt;p&gt;In this talk I dove into a few different areas exploring what actually makes up
a diagnostic, how useful Scala diagnostics are, how a diagnostic actually gets
from the compiler to your editor, and looked at what work is being done to improve
this. Much of this was a learning experience for me, so come along for the ride.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#what-actually-makes-up-a-diagnostic&quot;&gt;What actually makes up a diagnostic?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#the-diagnostic-usefulness-scale&quot;&gt;The diagnostic usefulness scale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-does-the-diagnostic-actually-get-to-your-editor&quot;&gt;How does the diagnostic actually get to your editor?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#where-is-scala-on-the-scale&quot;&gt;Where is Scala on the scale?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#what-else-is-on-the-scale&quot;&gt;What else is on the scale?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#its-all-about-structure&quot;&gt;It's all about structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#why-did-you-even-look-into-this&quot;&gt;Why did you even look into this?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#what-work-is-being-done-to-improve-this&quot;&gt;What work is being done to improve this?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#what-we-could-have&quot;&gt;What we could have&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#whats-keeping-this-from-happening&quot;&gt;What's keeping this from happening?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#what-actually-makes-up-a-diagnostic&quot; id=&quot;what-actually-makes-up-a-diagnostic&quot;&gt;What actually makes up a diagnostic?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Normally this would be the place I break down the reason for even caring about
diagnostics, but before I give you the actual use-case that spurred me to go
down this rabbit hole we need to go over the basics first and come to a common
understanding of what even makes up a diagnostic. Trust me, going through this
will make what's to come much clearer, so bear with me.&lt;/p&gt;
&lt;p&gt;Let's start with a very minimal code example.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;val word: String = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you don't need to know Scala well to know this won't work. If you're in your
editor you'd expect to see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/type-mismatch.png&quot; alt=&quot;type mismatch error&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Probably what you'd expect, but let's break this a part a bit and look at the
various parts.&lt;/p&gt;
&lt;p&gt;Firstly you have the message, which is what tells you what's actually wrong.
This is typically front and center when you're viewing your diagnostic.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/type-mismatch-msg.png&quot; alt=&quot;type mismatch msg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Secondly you have the severity level of your diagnostic. In our example here
this an error, and that's indicated by the &lt;em&gt;in your face&lt;/em&gt; red color. Typically
you're message may be in this color along with something indicating where it is
in your current file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/severity-error.png&quot; alt=&quot;showing that the diagnostic is an error&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You're also probably familiar with the warning diagnostic, typically signified
with a yellow/orange color as you can see here:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/severity-warning.png&quot; alt=&quot;showing that the diagnostic is a warning&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now, some that you're probably less familiar with in Scala are info diagnostics.
You can see a great example of this in the &lt;a href=&quot;https://getquill.io/&quot;&gt;quill&lt;/a&gt; library where you can
have a &lt;code&gt;query&lt;/code&gt; and it will actually show you what's being generated. In this
case it's indicated in blue.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/severity-info.png&quot; alt=&quot;showing that the diagnostic is an info&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is an incredibly useful type of diagnostic that isn't used much in the
Scala ecosystem. There's also hint diagnostics, but as far as I know, there
aren't used anywhere in the Scala ecosystem.&lt;/p&gt;
&lt;p&gt;Thirdly we have the position it occurs in. This is typically a combination of
marking the line number and the specific character or span that the error
appears on.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/position.png&quot; alt=&quot;showing the position of the diagnostic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;These three things more or less make up the core parts of a diagnostic. Without
one of these the diagnostic becomes quite a bit less useful.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-diagnostic-usefulness-scale&quot; id=&quot;the-diagnostic-usefulness-scale&quot;&gt;The diagnostic usefulness scale&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So now that we know the core parts of a diagnostic, let's create what I call
&lt;em&gt;The Diagnostic Usefulness Scale&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/diagnostic-usefulness-scale.png&quot; alt=&quot;diagnostic usefulness scale&quot; /&gt;&lt;/p&gt;
&lt;p&gt;On the far left we have essentially no help at all, and on the far right we have
&lt;em&gt;all of the help&lt;/em&gt;. Since what we covered make up the core parts of a diagnostic
let's just stick them in the middle. Realistically no useful language is going
to report less than this. Not reporting any of them, especially the position or
the message pretty much makes the diagnostic useless. So it's fair to say that
in reality our scale really looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/revised-diagnostic-usefulness-scale.png&quot; alt=&quot;revised diagnostic usefulness scale&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since our core parts now move all the way over to the left you're hopefully
focusing on the big area in the middle. I'd like to take a look at three
questions based off this.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Where is Scala on this scale?&lt;/li&gt;
&lt;li&gt;What else is on this scale?&lt;/li&gt;
&lt;li&gt;Where are other languages on this scale?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;a href=&quot;#how-does-the-diagnostic-actually-get-to-your-editor&quot; id=&quot;how-does-the-diagnostic-actually-get-to-your-editor&quot;&gt;How does the diagnostic actually get to your editor?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So before we can really dive into where Scala exactly is on this scale we need
to take a deeper look at both the actual structure of a diagnostic inside of
Dotty and also the journey it takes from the compiler all the way to your
editor. It's also important to realize that the way a diagnostic is reported
wildly depends on quite a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Are you in the REPL?&lt;/li&gt;
&lt;li&gt;Are you using X build tool?&lt;/li&gt;
&lt;li&gt;Are you using a build server?&lt;/li&gt;
&lt;li&gt;Are you using &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ?&lt;/a&gt; &lt;a href=&quot;https://scalameta.org/metals/&quot;&gt;Metals?&lt;/a&gt; &lt;a href=&quot;https://ensime.github.io/&quot;&gt;Ensime?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Or some combination of the above?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the purpose of this article we'll pretend that we have the below tool-chain:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/diagnostic-chain.png&quot; alt=&quot;chain of the entire diagnostic flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We'll start in the compiler and we'll take a look at some places we actually do
some reporting. First we'll look &lt;a href=&quot;https://github.com/lampepfl/dotty/blob/5bd67bb4f7b09ebfff6c48ecc6cdad67b85b4533/compiler/src/dotty/tools/dotc/typer/Checking.scala#L712-L716&quot;&gt;here in &lt;code&gt;Checking.scala&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;if (param.is(Erased))
  report.error(&amp;quot;value class first parameter cannot be `erased`&amp;quot;, param.srcPos)
else
  for (p &amp;lt;- params if !p.is(Erased))
    report.error(&amp;quot;value class can only have one non `erased` parameter&amp;quot;, p.srcPos)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how it's just reporting a string and a position. That's it. It's a super
quick way to report an error and looking around the Dotty code base I'd say this
is the most common way I see errors reported. There are instances however that
&lt;code&gt;Message&lt;/code&gt;s are reported as well. You can see an example &lt;a href=&quot;https://github.com/lampepfl/dotty/blob/5bd67bb4f7b09ebfff6c48ecc6cdad67b85b4533/compiler/src/dotty/tools/dotc/typer/Checking.scala#L468-L473&quot;&gt;here in
&lt;code&gt;Checking.scala&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;def fail(msg: Message) = report.error(msg, sym.srcPos)

def checkWithDeferred(flag: FlagSet) =
  if (sym.isOneOf(flag))
    fail(AbstractMemberMayNotHaveModifier(sym, flag))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see what
&lt;a href=&quot;https://github.com/lampepfl/dotty/blob/5bd67bb4f7b09ebfff6c48ecc6cdad67b85b4533/compiler/src/dotty/tools/dotc/reporting/messages.scala#L1482-L1487&quot;&gt;&lt;code&gt;AbstractMemberMayNotHaveModifier&lt;/code&gt;&lt;/a&gt;
looks like, but it's one of many &lt;code&gt;Message&lt;/code&gt;s that Scala has. Here's what the
&lt;a href=&quot;https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/dotc/reporting/Message.scala#L27-L144&quot;&gt;&lt;code&gt;Message&lt;/code&gt;&lt;/a&gt; class looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;abstract class Message(val errorId: ErrorMessageID) { self =&amp;gt;

  protected def msg: String

  def kind: MessageKind

  protected def explain: String

  protected def msgSuffix: String = &amp;quot;&amp;quot;

  def canExplain: Boolean = explain.nonEmpty

  private var myMsg: String | Null = null
  private var myIsNonSensical: Boolean = false

  private def dropNonSensical(msg: String): String = {...}

  def rawMessage = message

  @threadUnsafe lazy val message: String = dropNonSensical(msg + msgSuffix)

  @threadUnsafe lazy val explanation: String = dropNonSensical(explain)

  def isNonSensical: Boolean = { message; myIsNonSensical }

  def persist: Message = {...}

  def append(suffix: =&amp;gt; String): Message = mapMsg(_ ++ suffix)

  def mapMsg(f: String =&amp;gt; String): Message = {...}

  def appendExplanation(suffix: =&amp;gt; String): Message = {...}

  def showAlways = false

  override def toString = msg
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I've taken out all the comments to shorten up the snippet, but you should get
the idea. Pretty much everything inside of here is focused around the message of
the diagnostic that you end up seeing as an end user.&lt;/p&gt;
&lt;p&gt;Now let's take a look at how an actual &lt;a href=&quot;https://github.com/lampepfl/dotty/blob/5bd67bb4f7b09ebfff6c48ecc6cdad67b85b4533/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala#L88-L106&quot;&gt;&lt;code&gt;Diagnostic&lt;/code&gt;&lt;/a&gt; is
structured in Dotty.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;class Diagnostic(
  val msg: Message,
  val pos: SourcePosition,
  val level: Int
) extends Exception with interfaces.Diagnostic:
  private var verbose: Boolean = false
  def isVerbose: Boolean = verbose
  def setVerbose(): this.type =
    verbose = true
    this

  override def position: Optional[interfaces.SourcePosition] =
    if (pos.exists &amp;amp;&amp;amp; pos.source.exists) Optional.of(pos) else Optional.empty()
    
  override def message: String =
    msg.message.replaceAll(&amp;quot;\u001B\\[[;\\d]*m&amp;quot;, &amp;quot;&amp;quot;)

  override def toString: String = s&amp;quot;$getClass at $pos: $message&amp;quot;
  override def getMessage(): String = message
end Diagnostic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how the &lt;code&gt;Diagnostic&lt;/code&gt; actually holds the &lt;code&gt;Message&lt;/code&gt; and then you also see
the other main parts that we talked about earlier with the &lt;code&gt;pos&lt;/code&gt; and the
&lt;code&gt;level&lt;/code&gt;. So right here we again have the core parts of a diagnostic.&lt;/p&gt;
&lt;p&gt;Now already at this point the way this move forwards depends on the reporter.
Basically you could look at it in one of two ways:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/zinc-or-other.png&quot; alt=&quot;Arrow going to zinc or other&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Well why &lt;a href=&quot;https://github.com/sbt/zinc&quot;&gt;Zinc&lt;/a&gt;? Where does Zinc come into the picture? If you look into
the &lt;a href=&quot;https://github.com/lampepfl/dotty&quot;&gt;Dotty codebase&lt;/a&gt; you'll notice a directory, &lt;code&gt;sbt-bridge&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;❯ tree -L 5 sbt-bridge/
sbt-bridge
├── resources
│  └── META-INF
│     └── services
│        └── xsbti.compile.CompilerInterface2
└── src
   ├── dotty
   │  └── tools
   │     └── xsbt
   │        ├── CompilerBridge.java
   │        ├── CompilerBridgeDriver.java
   │        ├── DelegatingReporter.java
   │        ├── DiagnosticCode.java
   │        ├── InterfaceCompileFailed.java
   │        ├── PositionBridge.java
   │        ├── Problem.java
   │        ├── ZincPlainFile.java
   │        └── ZincVirtualFile.java
   └── xsbt
      ├── CachedCompilerImpl.java
      ├── CompilerClassLoader.java
      ├── CompilerInterface.java
      ├── ConsoleInterface.java
      ├── DottydocRunner.java
      └── ScaladocInterface.java
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well what is this? If you look into the &lt;a href=&quot;https://github.com/sbt/zinc&quot;&gt;Zinc&lt;/a&gt; &lt;code&gt;README.md&lt;/code&gt; you'll see the
following note:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The compiler bridge classes are loaded using java.util.ServiceLoader. In other
words, the class implementing xsbti.compile.CompilerInterface2 must be
mentioned in a file named:
/META-INF/services/xsbti.compile.CompilerInterface2.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And that's exactly what we're seeing here. The Dotty codebase actually
implements &lt;code&gt;ComplerInterface2&lt;/code&gt; so that anything using Zinc can basically
communicate with Dotty. The &lt;code&gt;xsbt&lt;/code&gt; directory is for the legacy version of Zinc,
but the &lt;code&gt;dotty.tools.xsbt&lt;/code&gt; is the new one that we're concerned about. So if we
have a &lt;code&gt;Diagnostic&lt;/code&gt; in Dotty we need to look at how this is represented as it
starts flowing and being reported through this bridge. To do that we need to take
yet another step back and look at the interfaces where this all starts. These
interfaces are actually a module in the sbt codebase called
&lt;a href=&quot;https://github.com/sbt/sbt/tree/1.8.x/internal/util-interface/src/main/java/xsbti&quot;&gt;&lt;code&gt;util-interface&lt;/code&gt;&lt;/a&gt;. In there you can find &lt;code&gt;Problem.java&lt;/code&gt;.
That &lt;code&gt;Problem&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface Problem {
  String category();

  Severity severity();

  String message();

  Position position();

  // Default value to avoid breaking binary compatibility
  /**
   * If present, the string shown to the user when displaying this Problem. Otherwise, the Problem
   * will be shown in an implementation-defined way based on the values of its other fields.
   */
  default Optional&amp;lt;String&amp;gt; rendered() {
    return Optional.empty();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, we'll notice the core parts of what we talked about earlier, the
message, the position, and the severity level. We do have a couple extras here,
the category and more importantly &lt;code&gt;rendered()&lt;/code&gt;. Note that rendered here will be
important when we talk about some extra information that Dotty shows when
running your code in a REPL or through your build tool, but &lt;em&gt;not&lt;/em&gt; through BSP.
There is a difference. If we look back at the little flow chain graph that we
said we're following we can actually label the arrows for each &amp;quot;protocol&amp;quot; or
&amp;quot;interface&amp;quot; that is defining how these things are being transferred. When
labeled it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/protocols.png&quot; alt=&quot;chain of flow with protocols&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So we know what &lt;code&gt;Problem&lt;/code&gt; looks like when it's passing through the &lt;code&gt;sbt-bridge&lt;/code&gt;,
which leaves us looking at what it looks like in the &lt;a href=&quot;https://microsoft.github.io/language-server-protocol/&quot;&gt;Language Server Protocol&lt;/a&gt;
(LSP) and the &lt;a href=&quot;https://build-server-protocol.github.io/&quot;&gt;Build Server Protocol&lt;/a&gt; (BSP). BSP was modeled after LSP so
the &lt;code&gt;Diagnostic&lt;/code&gt; in BSP &lt;em&gt;should&lt;/em&gt; be a replica of what's in LSP. Here's what it
looks like in &lt;a href=&quot;https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic&quot;&gt;LSP&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export interface Diagnostic {
	/** The range at which the message applies.*/
	range: Range;
	/**
	 * The diagnostic's severity. Can be omitted. If omitted it is up to the
	 * client to interpret diagnostics as error, warning, info or hint.
	 */
	severity?: DiagnosticSeverity;
	/** The diagnostic's code, which might appear in the user interface. */
	code?: integer | string;
	/** An optional property to describe the error code. */
	codeDescription?: CodeDescription;
	/**
	 * A human-readable string describing the source of this
	 * diagnostic, e.g. 'typescript' or 'super lint'.
	 */
	source?: string;
	/** The diagnostic's message. */
	message: string;
	/** Additional metadata about the diagnostic. */
	tags?: DiagnosticTag[];
	/**
	 * An array of related diagnostic information, e.g. when symbol-names within
	 * a scope collide all definitions can be marked via this property.
	 */
	relatedInformation?: DiagnosticRelatedInformation[];
	/**
	 * A data entry field that is preserved between a
	 * `textDocument/publishDiagnostics` notification and
	 * `textDocument/codeAction` request.
	 */
	data?: unknown;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point you should be able to assume exactly what I'm about to say. We
again have the main things we looked at before with the range, severity, and
the message. &lt;em&gt;However&lt;/em&gt; we're now introduced to some new things we haven't really
seen yet. We'll dig into these further later but take note of the &lt;code&gt;code&lt;/code&gt;,
&lt;code&gt;relatedInformation&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt; fields.&lt;/p&gt;
&lt;p&gt;Let's pretend we're using sbt now as our Build server. After the Dotty
&lt;code&gt;Diagnostic&lt;/code&gt; has been reported via the bridge and is being represented as a
&lt;code&gt;Problem&lt;/code&gt; internally for sbt it needs to then be reported over BSP. This means
it again it gets translated, this time into the BSP &lt;code&gt;Diagnostic&lt;/code&gt;. You can see
this happen here in the &lt;a href=&quot;https://github.com/sbt/sbt/blob/7921cab7e3bb252303b9e283cd40620fff322f19/main/src/main/scala/sbt/internal/server/BuildServerReporter.scala&quot;&gt;&lt;code&gt;BuildServerReporter.scala&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;protected override def publishDiagnostic(problem: Problem): Unit = {
  for {
  id &amp;lt;- problem.position.sourcePath.toOption
  diagnostic &amp;lt;- toDiagnostic(problem)
  filePath &amp;lt;- toSafePath(VirtualFileRef.of(id))
} {
  problemsByFile(filePath) = problemsByFile.getOrElse(filePath, Vector.empty) :+ diagnostic
  val params = PublishDiagnosticsParams(
    TextDocumentIdentifier(filePath.toUri),
    buildTarget,
    originId = None,
    Vector(diagnostic),
    reset = false
  )
  exchange.notifyEvent(&amp;quot;build/publishDiagnostics&amp;quot;, params)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the last line where the &lt;code&gt;build/publishDiagnostics&lt;/code&gt; event is being
emitted. This is how sbt as a build server is sending the diagnostics to
whatever client is current attached. In our example this client is Metals. Once
Metals receives it it will do a few things, like possibly some de-duplication,
but you'll then see it turn the BSP diagnostic into an LSP one
&lt;a href=&quot;https://github.com/scalameta/metals/blob/main/metals/src/main/scala/scala/meta/internal/metals/Diagnostics.scala&quot;&gt;here&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;private def publishDiagnostics(
    path: AbsolutePath,
    queue: ju.Queue[Diagnostic],
): Unit = {
  if (!path.isFile) return didDelete(path)
  val current = path.toInputFromBuffers(buffers)
  val snapshot = snapshots.getOrElse(path, current)
  val edit = TokenEditDistance(
    snapshot,
    current,
    trees,
    doNothingWhenUnchanged = false,
  )
  val uri = path.toURI.toString
  val all = new ju.ArrayList[Diagnostic](queue.size() + 1)
  for {
    diagnostic &amp;lt;- queue.asScala
    freshDiagnostic &amp;lt;- toFreshDiagnostic(edit, diagnostic, snapshot)
  } {
    all.add(freshDiagnostic)
  }
  // Removed some de-duplication stuff here to save room
  languageClient.publishDiagnostics(new PublishDiagnosticsParams(uri, all))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, the last line you see Metals publishing this diagnostic to the language
client, which in our case is your editor. So as you can see, the journey from
the compiler actually reporting the error goes through quite a few places and
representations before it actually hits your eyes.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#where-is-scala-on-the-scale&quot; id=&quot;where-is-scala-on-the-scale&quot;&gt;Where is Scala on the Scale&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we've seen the structure of the actual Scala diagnostic coming from the
compiler we can sort of gauge where Scala is at on our &lt;a href=&quot;#the-diagnostic-usefulness-scale&quot;&gt;diagnostic usefulness
scale&lt;/a&gt;. I'd probably put it around here:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/diagnostic-scale-scala-location.png&quot; alt=&quot;Diagnostic scale with Scala location&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But hold on a second&lt;/em&gt;. Maybe this isn't fair as there are a few extra things
that Scala 3 does. Let's take a look at these. We'll take a simple piece of
Scala code that as you know, won't work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;trait Foo:
  def hello(): Unit
  def goodBye(): Unit

class Greeting extends Foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We'll use &lt;a href=&quot;https://scala-cli.virtuslab.org/&quot;&gt;&lt;code&gt;scala-cli&lt;/code&gt;&lt;/a&gt; and run this snippet. You'll see the
following output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Compiling project (Scala 3.2.0, JVM)
[error] ./demo.scala:5:7: class Greeting needs to be abstract, since:
[error] it has 2 unimplemented members.
[error] /** As seen from class Greeting, the missing signatures are as follows.
[error]  *  For convenience, these are usable as stub implementations.
[error]  */
[error]   def goodBye(): Unit = ???
[error]   def hello(): Unit = ???
[error] class Greeting extends Foo
[error]       ^
Error compiling project (Scala 3.2.0, JVM)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that the error message actually has stub implementations that you could
copy and paste into your code to make the code compile. This is a helpful little
message.&lt;/p&gt;
&lt;p&gt;Let's look at another one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;trait Foo[A]:
  def foo: Int

def foo =
  second[String]

inline def first[A]: Int =
  compiletime.summonInline[Foo[A]].foo

inline def second[A]: Int =
  first[A] + 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one is a bit more interesting and we'll show the error it produces in a few
different ways. Firstly, in your editor you'll just see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;No given instance of type Foo[String] was found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same if you run it with &lt;code&gt;scala-cli&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Compiling project (Scala 3.2.0, JVM)
[error] ./demo.scala:5:3: No given instance of type Foo[String] was found
[error]   second[String]
[error]   ^^^^^^^^^^^^^^
Error compiling project (Scala 3.2.0, JVM)
Compilation failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But now notice the difference if you run it with &lt;code&gt;scala&lt;/code&gt; directly, sbt, or Mill:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;-- Error: /Users/ckipp/Documents/scala-workspace/scripts/demo.scala:5:8 --------
 5 |  second[String]
   |  ^^^^^^^^^^^^^^
   |  No given instance of type Foo[String] was found
   |----------------------------------------------------------------------------
   |Inline stack trace
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from demo.scala:8
 8 |  compiletime.summonInline[Foo[A]].foo
   |  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from demo.scala:8
11 |  first[A] + 1
   |  ^^^^^^^^
    ----------------------------------------------------------------------------
1 error found
Errors encountered during compilation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we get some extra information about the inlined code and where that inline code
is located. This is a very simple example, but imagine a much larger code base.
This could be incredibly useful! The difference here is whether or not the
report is using the &lt;code&gt;rendered()&lt;/code&gt; method to get the diagnostic message or just
grabbing the actual message.&lt;/p&gt;
&lt;p&gt;So with this new information maybe it's a bit fair to bump Scala over a bit on
our Diagnostic Usefulness Scale:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/diagnostic-scale-scala-location-updated.png&quot; alt=&quot;Scala's new position on the diagnostic usefulness scale&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#what-else-is-on-the-scale&quot; id=&quot;what-else-is-on-the-scale&quot;&gt;What else is on the scale?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point you're probably starting to wonder what else is actually on this
scale? What other languages have more to offer than what we're seeing. In order
to explore this we'll again dive into a small code sample. We'll start with
Scala:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;object Main:
  val greeting = &amp;quot;hello&amp;quot;

  greeting = &amp;quot;hi&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm using this snippet both for its brevity but also because as minimal as it
is, it'll show exactly what we want. Let's look at what we get using VS Code
with Metals for this diagnostic:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/scala-minimal-diagnostic.png&quot; alt=&quot;A minimal Scala diagnostic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This of course tells you the issue, but if you're like me you might be asking a
few more questions when looking at this diagnostic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Where is the original assignment?&lt;/li&gt;
&lt;li&gt;Can I get a more detailed explanation of this error?&lt;/li&gt;
&lt;li&gt;What are my alternatives to fix this?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;None of these questions are really answered by this diagnostic. But let's take a
look at another language. We'll use Rust with essentially the same snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main() {

    let greeting = &amp;quot;hello&amp;quot;;

    greeting = &amp;quot;hi&amp;quot;;
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your editor (using VS Code again) you'll see the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/rust-minimal-diagnostic.png&quot; alt=&quot;A minimal Rust diagnostic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now let's revising those questions we had earlier. Firstly, &lt;em&gt;Where is the
original assignment?&lt;/em&gt; Notice that this is actually included in the Rust
diagnostic:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/rust-minimal-diagnostic-where.png&quot; alt=&quot;A minimal Rust diagnostic showing where the original assignment was&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That's pretty cool. Not only does it tell me, but I can just click and jump to
it. Perfect. Again, this is a tiny example, but imagine if this was a huge code
base. What about the next question, &lt;em&gt;Can I get a more detailed explanation of
this error?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/rust-minimal-diagnostic-explain.png&quot; alt=&quot;A minimal Rust diagnostic showing where to find more information&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So there is a short error message which is pretty clear, but notice the &lt;code&gt;E0384&lt;/code&gt;
here. What is this? It's also at this point that I'm curious if you've ever
noticed this in Scala. When running your code have you ever seen the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;-- [E003] Syntax Deprecation Warning: examples/003_DeprecatedWithOperatorID.scala:4:18 
4 |  type A = Double with Int
  |                  ^
  |                with as a type operator has been deprecated; use &amp;amp; instead
1 warning found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;[E003]&lt;/code&gt; here. I'm going to guess that most people just gloss right
over this, but it's actually important... but unused. In Scala 3 &lt;em&gt;some&lt;/em&gt;
diagnostics have a unique ID attached to them. In this case usage of &lt;code&gt;with&lt;/code&gt; is
deprecated and the unique ID signifying that is &lt;code&gt;003&lt;/code&gt;. Well where is &lt;code&gt;003&lt;/code&gt;
documented? It's not. It's also not used literally anywhere at the moment in the
Scala ecosystem (as far as I know). But let's look at how it &lt;em&gt;is&lt;/em&gt; used in Rust.
In the Rust diagnostic this is actually a clickable link that will bring you to
the &lt;a href=&quot;https://doc.rust-lang.org/error_codes/error-index.html&quot;&gt;Rust error codes index&lt;/a&gt;. If we click on &lt;code&gt;E0384&lt;/code&gt; we're
brought to a section with a more detailed explanation of the error message and a
runnable code snippet to reproduce it. Again, this is a simple error that we've
chosen, but imagine if this was a more involved error message that might be hard
for users to understand. Think of newcomers to a language that might not be
familiar with all the lingo and concepts a language has to offer. How incredibly
useful this could be. There currently is no official thing like this for Scala.&lt;/p&gt;
&lt;p&gt;The final question we asked was, &lt;em&gt;What are my alternatives?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/rust-minimal-diagnostic-alt.png&quot; alt=&quot;A minimal Rust diagnostic showing how to fix the issue&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Notice that in this diagnostic it even includes a possible way to fix it. This
is similar to what we saw up above with the stubs for Scala, but wait, there's a
bonus here. If you click the link to go to where the first assignment of
&lt;code&gt;greeting&lt;/code&gt; is you'll notice there's a quickfix attatched to the position that
will actually just fix it for you by adding a &lt;code&gt;mut&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/rust-fix-for-me.png&quot; alt=&quot;Rust quick fix for adding mut&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There is a common pattern you'll see in some languages like Scala where the
compiler knows exactly a way to fix your problem, but doesn't expose this
information in an structured way. You're always left with the information just
in a string with other information. Rust seemingly does a great job at this by
not only returning the information, but returning it in a structural way that
tools and therefore users can benefit from.&lt;/p&gt;
&lt;p&gt;Let's jump back to our scale. If we were to drop Rust on here I'd probably drop
it much further to the right than Scala.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/rust-and-scala-on-scale.png&quot; alt=&quot;Rust and Scala on the usefulness scale&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#its-all-about-structure&quot; id=&quot;its-all-about-structure&quot;&gt;It's all about Structure&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point hopefully you're wondering why this all is. Why doesn't Scala 3
have some of these niceties. The answer to this is sort of twofold. Firstly
there's a mismatch in priority between compiler development speed (meaning those
working on the compiler) vs the desired to have &lt;strong&gt;more structured output&lt;/strong&gt;. It's
all about structure, and I'll show you why.&lt;/p&gt;
&lt;p&gt;Up &lt;a href=&quot;#how-does-the-diagnostic-actually-get-to-your-editor&quot;&gt;above&lt;/a&gt; we went through
the full flow and structure of a Dotty diagnostic. Let's now look at the
structure of a Rust one and compare. The core Diagnostic can be found here in
&lt;a href=&quot;https://github.com/rust-lang/rust/blob/e96c330af5e3baebe7a80999744e2e082b279d0a/compiler/rustc_errors/src/json.rs#L211-L222&quot;&gt;json.rs&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct Diagnostic {
    /// The primary error message.
    message: String,
    code: Option&amp;lt;DiagnosticCode&amp;gt;,
    /// &amp;quot;error: internal compiler error&amp;quot;, &amp;quot;error&amp;quot;, &amp;quot;warning&amp;quot;, &amp;quot;note&amp;quot;, &amp;quot;help&amp;quot;.
    level: &amp;amp;'static str,
    spans: Vec&amp;lt;DiagnosticSpan&amp;gt;,
    /// Associated diagnostic messages.
    children: Vec&amp;lt;Diagnostic&amp;gt;,
    /// The message as rustc would render it.
    rendered: Option&amp;lt;String&amp;gt;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks pretty familiar and more or less what you'd expect. Note that we do
have a possible &lt;code&gt;DiagnosticCode&lt;/code&gt; here that is getting forward, so let's look at
what &lt;a href=&quot;https://github.com/rust-lang/rust/blob/e96c330af5e3baebe7a80999744e2e082b279d0a/compiler/rustc_errors/src/json.rs#L276-L281&quot;&gt;that&lt;/a&gt; looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct DiagnosticCode {
    /// The code itself.
    code: String,
    /// An explanation for the code.
    explanation: Option&amp;lt;&amp;amp;'static str&amp;gt;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks strangely similar to the LSP one! I'll also again bring up the &lt;a href=&quot;https://doc.rust-lang.org/error_codes/error-index.html&quot;&gt;Rust
Error index&lt;/a&gt; that has a full listing of the Diagnostics that
are reported by Rust, with explanations, and runnable code samples.&lt;/p&gt;
&lt;p&gt;But let's keep going because there are some more things in the &lt;code&gt;Diagnostic&lt;/code&gt;
which are new to use. The next one is the
&lt;a href=&quot;https://github.com/rust-lang/rust/blob/master/compiler/rustc_errors/src/json.rs#L225-L249&quot;&gt;&lt;code&gt;DiagnosticSpan&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct DiagnosticSpan {
    file_name: String,
    byte_start: u32,
    byte_end: u32,
    /// 1-based.
    line_start: usize,
    line_end: usize,
    /// 1-based, character offset.
    column_start: usize,
    column_end: usize,
    /// Is this a &amp;quot;primary&amp;quot; span -- meaning the point, or one of the points,
    /// where the error occurred?
    is_primary: bool,
    /// Source text from the start of line_start to the end of line_end.
    text: Vec&amp;lt;DiagnosticSpanLine&amp;gt;,
    /// Label that should be placed at this location (if any)
    label: Option&amp;lt;String&amp;gt;,
    /// If we are suggesting a replacement, this will contain text
    /// that should be sliced in atop this span.
    suggested_replacement: Option&amp;lt;String&amp;gt;,
    /// If the suggestion is approximate
    suggestion_applicability: Option&amp;lt;Applicability&amp;gt;,
    /// Macro invocations that created the code at this span, if any.
    expansion: Option&amp;lt;Box&amp;lt;DiagnosticSpanMacroExpansion&amp;gt;&amp;gt;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some of this is familiar, but we do have some new interesting things. Most
interesting to me is the &lt;code&gt;suggested_replacement&lt;/code&gt;. Remember earlier when we
talked about the compiler knowing &lt;em&gt;how&lt;/em&gt; to actually fix this at times and maybe
putting it into the message. Here's a great example of how this could be
returned structurally. You'll also see there is a &lt;code&gt;suggestion_applicability&lt;/code&gt;
that explains the &amp;quot;confidence&amp;quot; in the correctness of the suggestion. I'll make a
note here that this isn't just Rust doing this. This goes back &lt;em&gt;way&lt;/em&gt; further.
For example we can look at &lt;em&gt;Fix-it&lt;/em&gt; hints from Clang. Here is a snippet from the
&lt;a href=&quot;https://clang.llvm.org/diagnostics.html&quot;&gt;Clang docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;Fix-it&amp;quot; hints provide advice for fixing small, localized problems in source
code. When Clang produces a diagnostic about a particular problem that it can
work around (e.g., non-standard or redundant syntax, missing keywords, common
mistakes, etc.), it may also provide specific guidance in the form of a code
transformation to correct the problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note that in the &lt;code&gt;DiagnosticSpan&lt;/code&gt; there is also &lt;code&gt;is_primary&lt;/code&gt; in order to
indicate if one span is the &amp;quot;primary&amp;quot; span or not. This is helpful when
reporting multiple different diagnostic spans tied to one diagnostic.&lt;/p&gt;
&lt;p&gt;Finally take note that in the &lt;code&gt;Diagnostic&lt;/code&gt; there is another new field,
&lt;code&gt;children&lt;/code&gt;. This &lt;code&gt;children&lt;/code&gt; shows that a single &lt;code&gt;Diagnostic&lt;/code&gt; can also hold other
diagnostics. Another thing that is currently unrepresentable in Dotty.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#why-did-you-even-look-into-this&quot; id=&quot;why-did-you-even-look-into-this&quot;&gt;Why did you even look into this?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point you may be wondering &lt;em&gt;why&lt;/em&gt;? What got you focused on this? So join
me in going back a while and looking at a real-world tooling use-case that
starting me down this path of exploring what diagnostics actually look like in
Dotty, and then coming to the realization that we can do much better.
Back in early 2021 &lt;a href=&quot;https://twitter.com/gabro27&quot;&gt;Gabriele Petronella&lt;/a&gt; sent in a &lt;a href=&quot;https://github.com/scalameta/metals/pull/2536&quot;&gt;PR to
Metals&lt;/a&gt; to take an error from the compiler about a
missing import and create a &lt;a href=&quot;https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction&quot;&gt;Code Action&lt;/a&gt; out of it. This code
action feature is the same type of feature you saw up above with Rust and the
Quick Fix example. During this process &lt;a href=&quot;https://github.com/scalameta/metals/pull/2536#discussion_r583848840&quot;&gt;this comment&lt;/a&gt; is what
sparked it for me:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/smarter-comment.png&quot; alt=&quot;Screenshot of comment by @smarter&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The main issue here is that in Metals in order to offer certain Code Actions we
need to first recognize what type of diagnostic is being reported. As you saw
earlier, there is a unique ID, but it's not forwarded at all. So we do things
like &lt;a href=&quot;https://github.com/scalameta/metals/blob/61fefaae878abcc1f7ca1efed06506b832b86248/metals/src/main/scala/scala/meta/internal/metals/ScalacDiagnostic.scala#L31-L55&quot;&gt;this&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;object TypeMismatch {
  private val regexStart = &amp;quot;&amp;quot;&amp;quot;type mismatch;&amp;quot;&amp;quot;&amp;quot;.r
  private val regexMiddle = &amp;quot;&amp;quot;&amp;quot;(F|f)ound\s*: (.*)&amp;quot;&amp;quot;&amp;quot;.r
  private val regexEnd = &amp;quot;&amp;quot;&amp;quot;(R|r)equired: (.*)&amp;quot;&amp;quot;&amp;quot;.r

  def unapply(d: l.Diagnostic): Option[(String, l.Diagnostic)] = {
    d.getMessage().split(&amp;quot;\n&amp;quot;).map(_.trim()) match {
      /* Scala 3:
       * Found:    (&amp;quot;&amp;quot; : String)
       * Required: Int
       */
      case Array(regexMiddle(_, toType), regexEnd(_, _)) =&amp;gt;
        Some((toType.trim(), d))
      /* Scala 2:
       * type mismatch;
       * found   : Int(122)
       * required: String
       */
      case Array(regexStart(), regexMiddle(_, toType), regexEnd(_, _)) =&amp;gt;
        Some((toType.trim(), d))
      case _ =&amp;gt;
        None
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This recognizes situations where maybe you changed a method and now the explicit
return type doesn't fit. So it offers you a code action to quickly change the
return type to the new expected type. Now this probably seems a bit crazy, but
when you have nothing but a string to work with, tooling is left to do gross
things like this. Hacks like this are flaky and error-prone not only because
they are regex, but because error messages can change. Imagine how much easier
it'd be if we had access to a unique ID to know what type of diagnostic we were
dealing with. We could rid tooling of these types of hacks. Now imagine if they
even came with a suggested fix! Crazy talk.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#what-work-is-being-done-to-improve-this&quot; id=&quot;what-work-is-being-done-to-improve-this&quot;&gt;What work is being done to improve this?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The good news is that there has been some progress made in this area. Up
&lt;a href=&quot;#how-does-the-diagnostic-actually-get-to-your-editor&quot;&gt;above&lt;/a&gt; we saw the
structures that were used all the way from the Dotty compiler to your editor.
These were a bit older since in recent months they've changed slightly. For
example the new &lt;a href=&quot;https://github.com/sbt/sbt/blob/09e06c45f01a72ed1010873a3ba89c0b70689fca/internal/util-interface/src/main/java/xsbti/Problem.java#L32-L50&quot;&gt;&lt;code&gt;Problem.java&lt;/code&gt;&lt;/a&gt; got a couple new fields:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
 * The unique code attached to the diagnostic being reported.
 *
 * &amp;lt;p&amp;gt;NOTE: To avoid breaking compatibility we provide a default to account for older Scala
 * versions that do not have codes.
 */
default Optional&amp;lt;DiagnosticCode&amp;gt; diagnosticCode() {
  return Optional.empty();
}

/**
 * The possible releated information for the diagnostic being reported.
 *
 * &amp;lt;p&amp;gt;NOTE: To avoid breaking compatibility we provide a default to account for older Scala
 * versions that do not have the concept of &amp;quot;related information&amp;quot;.
 */
default List&amp;lt;DiagnosticRelatedInformation&amp;gt; diagnosticRelatedInformation() {
  return Collections.emptyList();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now have the ability to have a &lt;code&gt;DiagnosticCode&lt;/code&gt; just like we saw in Rust and
also the ability to &lt;em&gt;nest&lt;/em&gt; diagnostics with the new
&lt;code&gt;DiagnosticRelatedInformation&lt;/code&gt;. This is great, but it also took a crazy amount
of changes just to get the code forwarded all the way to the editor. Take a
look:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In sbt (and zinc) - &lt;a href=&quot;https://github.com/sbt/sbt/pull/6874&quot;&gt;https://github.com/sbt/sbt/pull/6874&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In Dotty - &lt;a href=&quot;https://github.com/lampepfl/dotty/pull/14965&quot;&gt;https://github.com/lampepfl/dotty/pull/14965&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;More Dotty - &lt;a href=&quot;https://github.com/lampepfl/dotty/pull/15728&quot;&gt;https://github.com/lampepfl/dotty/pull/15728&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Changes in BSP - &lt;a href=&quot;https://github.com/build-server-protocol/build-server-protocol/pull/320&quot;&gt;https://github.com/build-server-protocol/build-server-protocol/pull/320&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In sbt (server) - &lt;a href=&quot;https://github.com/sbt/sbt/pull/6998&quot;&gt;https://github.com/sbt/sbt/pull/6998&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In Bloop - &lt;a href=&quot;https://github.com/scalacenter/bloop/pull/1750&quot;&gt;https://github.com/scalacenter/bloop/pull/1750&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In Mill - &lt;a href=&quot;https://github.com/sbt/sbt/pull/6874&quot;&gt;https://github.com/sbt/sbt/pull/6874&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In Metals - &lt;a href=&quot;https://github.com/scalameta/metals/pull/4239&quot;&gt;https://github.com/scalameta/metals/pull/4239&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this &lt;em&gt;just&lt;/em&gt; to get a single thing reported. Here it is, in all of its
glory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;diagnostics&amp;quot;: [
  {
    &amp;quot;range&amp;quot;: {
      &amp;quot;start&amp;quot;: {
        &amp;quot;line&amp;quot;: 9,
        &amp;quot;character&amp;quot;: 15
      },
      &amp;quot;end&amp;quot;: {
        &amp;quot;line&amp;quot;: 9,
        &amp;quot;character&amp;quot;: 19
      }
    },
    &amp;quot;severity&amp;quot;: 1,
    &amp;quot;code&amp;quot;: &amp;quot;7&amp;quot;,
    &amp;quot;source&amp;quot;: &amp;quot;sbt&amp;quot;,
    &amp;quot;message&amp;quot;: &amp;quot;Found:    (\u001b[32m\&amp;quot;hi\&amp;quot;\u001b[0m : String)\nRequired: Int\n\nThe following import might make progress towards fixing the problem:\n\n  import sourcecode.Text.generate\n\n&amp;quot;
  }
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are some other tools in the ecosystem that are starting to jump on board
with this idea of diagnostics that can just include a fix. For example
&lt;a href=&quot;https://scala-cli.virtuslab.org/&quot;&gt;scala-cli&lt;/a&gt; has one that was somewhat recently added to metals by
&lt;a href=&quot;https://github.com/lwronski&quot;&gt;&lt;code&gt;@lwronski&lt;/code&gt;&lt;/a&gt; that given a &lt;code&gt;using lib&lt;/code&gt; directive that is out of date,
&lt;code&gt;scala-cli&lt;/code&gt; will actually provide a diagnostic not only notifying the user that
it's out of date, but offering a quick fix to update it to the latest:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/scala-cli-diagnostic.png&quot; alt=&quot;scala-cli actionable diagnostic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is a killer feature that I wish more tools would offer, especially the
compiler. If you're curious under the hood what the diagnostic for this look
like, here you go:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;diagnostics&amp;quot;: [
    {
      &amp;quot;range&amp;quot;: {
        &amp;quot;start&amp;quot;: {
          &amp;quot;line&amp;quot;: 0,
          &amp;quot;character&amp;quot;: 15
        },
        &amp;quot;end&amp;quot;: {
          &amp;quot;line&amp;quot;: 0,
          &amp;quot;character&amp;quot;: 40
        }
      },
      &amp;quot;severity&amp;quot;: 4,
      &amp;quot;source&amp;quot;: &amp;quot;scala-cli&amp;quot;,
      &amp;quot;message&amp;quot;: &amp;quot;com.lihaoyi::os-lib:0.7.8 is outdated, update to 0.8.1\n     com.lihaoyi::os-lib:0.7.8 -\u003e com.lihaoyi::os-lib:0.8.1&amp;quot;,
      &amp;quot;data&amp;quot;: {
        &amp;quot;range&amp;quot;: {
          &amp;quot;start&amp;quot;: {
            &amp;quot;line&amp;quot;: 0,
            &amp;quot;character&amp;quot;: 15
          },
          &amp;quot;end&amp;quot;: {
            &amp;quot;line&amp;quot;: 0,
            &amp;quot;character&amp;quot;: 40
          }
        },
        &amp;quot;newText&amp;quot;: &amp;quot;com.lihaoyi::os-lib:0.8.1&amp;quot;
      }
    }
  ],
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;#what-we-could-have&quot; id=&quot;what-we-could-have&quot;&gt;What we could have&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let's revisit one of the example we had &lt;a href=&quot;#where-is-scala-on-the-scale&quot;&gt;up above&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;trait Foo:
  def hello(): Unit
  def goodBye(): Unit

class Greeting extends Foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actual diagnostic structure you end up getting from your build server about
this looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;diagnostics&amp;quot;: [
    {
      &amp;quot;range&amp;quot;: {
        &amp;quot;start&amp;quot;: {
          &amp;quot;line&amp;quot;: 11,
          &amp;quot;character&amp;quot;: 6
        },
        &amp;quot;end&amp;quot;: {
          &amp;quot;line&amp;quot;: 11,
          &amp;quot;character&amp;quot;: 14
        }
      },
      &amp;quot;severity&amp;quot;: 1,
      &amp;quot;source&amp;quot;: &amp;quot;bloop&amp;quot;,
      &amp;quot;message&amp;quot;: &amp;quot;class Greeting needs to be abstract, since:\nit has 2 unimplemented members.\n/** As seen from class Greeting, the missing signatures are as follows.\n *  For convenience, these are usable as stub implementations.\n */\n  def goodBye(): Unit \u003d ???\n  def hello(): Unit \u003d ???\n&amp;quot;
    }
  ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But what if instead of just the &lt;code&gt;message&lt;/code&gt; containing all this information the
error reported from the compiler looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;diagnostics&amp;quot;: [
    {
      &amp;quot;range&amp;quot;: {
        &amp;quot;start&amp;quot;: { ... },
        &amp;quot;end&amp;quot;: { ... }
      },
      &amp;quot;severity&amp;quot;: 1,
      &amp;quot;code&amp;quot;: &amp;quot;034&amp;quot;,
      &amp;quot;source&amp;quot;: &amp;quot;dotty&amp;quot;,
      &amp;quot;message&amp;quot;: &amp;quot;class Greeting needs to be abstract, since:\nit has 2 unimplemented members.\n/** As seen from class Greeting, the missing signatures are as follows.\n *  For convenience, these are usable as stub implementations.\n */\n&amp;quot;,
      &amp;quot;data&amp;quot;: {
        &amp;quot;range&amp;quot;: {
          &amp;quot;start&amp;quot;: { ... },
          &amp;quot;end&amp;quot;: { ... }
        },
        &amp;quot;newText&amp;quot;: &amp;quot;def goodBye(): Unit \u003d ???\n  def hello(): Unit \u003d ???\n&amp;quot;
      }
    }
  ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Imagine just being able to click a quick fix and get these stubs automatically
without needing to manually copy anything.&lt;/p&gt;
&lt;p&gt;Remember the &lt;a href=&quot;https://doc.rust-lang.org/error_codes/error-index.html&quot;&gt;Rust error index&lt;/a&gt;? What if we had something like
this for Scala? Something that was searchable, had examples, and was easy to
use? With the recent obsession with newcomers in Scala 3 I'm quite surprised that
this doesn't exist. If you're curious about what this could look like, I
actually have a WIP version of one in
&lt;a href=&quot;https://github.com/ckipp01/dotty-error-index&quot;&gt;ckipp01/dotty-error-index&lt;/a&gt;. Please feel free to contribute!&lt;/p&gt;
&lt;p&gt;Also remember the diagnostic example we showed from Dotty that had inlined code
in another box below the main message? What if we used
&lt;code&gt;diagnosticRelatedInformation&lt;/code&gt; for this instead. By doing that we'd
automatically get extra navigation possibilities in editors like VS Code. Here
is an example showing that:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/nested-diagnostic.gif&quot; alt=&quot;nested diagnostic navigation&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#whats-keeping-this-from-happening&quot; id=&quot;whats-keeping-this-from-happening&quot;&gt;What's keeping this from happening?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I slightly touched on this up above in the &lt;a href=&quot;#its-all-about-structure&quot;&gt;It's All About
Structure&lt;/a&gt; section, but I believe the major things
preventing some of this from happening are twofold.&lt;/p&gt;
&lt;p&gt;Firstly, the compiler team has to actually buy into this idea. While I can say
from experience that some totally have! But I can also say that some totally
haven't. There was a big push like 6 years ago to add missing error message IDs
and to have stable identifiers for each message. I commend this effort so early
on. It made very slow progress for a while until in 2020 it was &lt;a href=&quot;https://github.com/lampepfl/dotty/issues/1589#issuecomment-593879892&quot;&gt;abruptly
stopped&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The architecture for errors described in this issue proved to be heavy and
impractical. The migration process frequently interferes with in-flight PRs
and the benefits are marginal. We should find a more light-weight way to
represent errors.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This same mindset is seemingly still around where the idea of forcing stable
identifiers here is a chore and not worth the effort. You can see some
conversations around this in various places like
&lt;a href=&quot;https://github.com/lampepfl/dotty/pull/14965#issuecomment-1134390068&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While you can make the argument early on that pushing for stable identifiers
like this may have slightly hindered the speed of getting
things merged in, this mindset is also putting structured diagnostic reporting
as a hindsight instead of something baked right into the language. It's a shame
that while the error messages themselves have greatly improved in Scala 3, the
structures is essentially identical to what it's been for as long Scala has been
around. That's maybe alright for a research language, but it doesn't cut it in
the industry where developers are expecting a level of help and structure that
they get with languages like Rust.&lt;/p&gt;
&lt;p&gt;I mentioned early in the articles that the most common way you'll see an error
reported in Scala 3 is simply by taking a string and a position and reporting it
like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;if (param.is(Erased))
  report.error(&amp;quot;value class first parameter cannot be `erased`&amp;quot;, param.srcPos)
else
  for (p &amp;lt;- params if !p.is(Erased))
    report.error(&amp;quot;value class can only have one non `erased` parameter&amp;quot;, p.srcPos)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this is by far the quickest way to report, it's only good for that. Under
the hood it creates a &lt;code&gt;-1&lt;/code&gt; error code, which leaves us with an unusable error
ID. There needs to be a push to force stable identifies and care taken to ensure
unique error ids don't &lt;a href=&quot;https://github.com/lampepfl/dotty/issues/16130&quot;&gt;just disappear&lt;/a&gt; in favor of this style of
reporting.&lt;/p&gt;
&lt;p&gt;The second main blocker here is simply having someone focus and work on it.
While it's easy to point to the community and say, &amp;quot;someone just needs to step
up and do it&amp;quot;, changes like introducing &lt;code&gt;diagnosticRelatedInformation&lt;/code&gt; require
a decent amount of refactoring, primarily due to this trying to be hacked in
after the fact instead of being baked into reporting from the start. So
knowledge of the actual compiler internals are necessary for this. &lt;em&gt;Thankfully&lt;/em&gt;
there are ongoing efforts like the &lt;a href=&quot;https://scala-lang.org/blog/2022/11/02/compiler-academy.html&quot;&gt;Scala 3 Compiler Academy&lt;/a&gt;
and also people like &lt;a href=&quot;https://github.com/smarter&quot;&gt;@smarter&lt;/a&gt; that seem behind this idea and are
willing to help. This is incredibly important as without support from the actual
compiler team, there's no way this will get into the compiler.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you made it this far, bravo! I'm hoping that you now have a bit more clarity
on what makes up a diagnostic, how that diagnostic gets to your editor when
using the tooling chain we outlined above, and also an eagerness to see more
structured and actionable diagnostics coming from the compiler. The entire
ecosystem will benefit from this, from newcomers to experts alike. If you'd like
to follow along with the ongoing work in this area, you can follow the &lt;a href=&quot;https://github.com/lampepfl/dotty/issues/14904&quot;&gt;Progress
Issue&lt;/a&gt; about this in the Dotty repo.&lt;/p&gt;
&lt;p&gt;Cheers, and thanks for following along.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/the-journey-of-a-dotty-diagnostic</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/the-journey-of-a-dotty-diagnostic</guid><pubDate>Sun, 20 Nov 2022 00:00:00 +0100</pubDate></item><item><title>Music of the Month - October 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - October 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - October 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---october-2022&quot; id=&quot;music-of-the-month---october-2022&quot;&gt;Music of the Month - October 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I missed September. I was on holiday for most of, was traveling quite a bit, and
then got sick. Life happens. On to October. This last month I was also all over
for music. Honestly no one album really dominated, so I decided to pick one of
my favorites that I recently got on vinyl. Enjoy this month.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are few bands out there that have had more of an impact on me than &lt;a href=&quot;https://thiswilldestroyyoumusic.com/&quot;&gt;This
Will Destroy You&lt;/a&gt;. I discovered them early
on in college and was just floored by the amount of emotion they put into their
music and the sheer energy you feel when listening to them. While this album
isn't my favorite of theirs it was one I listened to a lot this past month since
I ordered it a long time ago to my parents house, but haven't been back to the
States to retrieve it for years. Since this last month brought me back to the
US, I was finally able to bring it home with me. The album of the month this
month is &lt;a href=&quot;https://thiswilldestroyyou.bandcamp.com/album/new-others-part-two&quot;&gt;&lt;em&gt;New Others - Part
Two&lt;/em&gt;&lt;/a&gt;. It's
definitely an album you'll want to listen to in its entirety and ideally front
to back.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; max-width: 350px; height: 687px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=1674374500/size=large/bgcol=333333/linkcol=ffffff/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://thiswilldestroyyou.bandcamp.com/album/new-others-part-two&quot;&gt;New Others Part Two by This Will Destroy You&lt;/a&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This months &amp;quot;Song of the Month&amp;quot; came by surprise. I came across a song on my
weekly Spotify playlist that I really enjoyed. It went on to my seasonal
playlist (I make one every season and have been since 2015), and I found myself
playing it over an over. When I looked up the band, &lt;a href=&quot;https://en.wikipedia.org/wiki/Casino_Versus_Japan&quot;&gt;Casino Versus
Japan&lt;/a&gt;, I found out he's
located less than an hour away from where I grew up in Wisconsin. His song
&lt;em&gt;Marilyn Set Me Free&lt;/em&gt; is the &amp;quot;Song of the Month&amp;quot; for me this month. Super chill
song that sort of makes you feel as if you're floating and grooving all at the
same time. I want a full album of this vibe.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; max-width: 350px; height: 470px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=3771486512/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/track=461733032/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://attacknine.bandcamp.com/album/hitori-kaiso-1998-2001&quot;&gt;Hitori + Kaiso: 1998 - 2001 by Casino Versus Japan&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-october-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-october-2022</guid><pubDate>Sun, 6 Nov 2022 00:00:00 +0100</pubDate></item><item><title>Scala Links</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;A collection of links that lead you to the various Scala pages, documentations, repos, etc.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Scala Links&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;A collection of links that lead you to the various Scala pages, documentations, repos, etc.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Scala Links&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#scala-links&quot; id=&quot;scala-links&quot;&gt;Scala Links&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The amount of times I need to look this stuff up drives me mad. So here's a
collection of Scala-related links to the various official pages, documentation
sites, repos, etc. Useful if you need to periodically go to all these places
like I do for contributing, reference, or desire for documentation. There a lot.
Some would say too many.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#main-websites&quot; id=&quot;main-websites&quot;&gt;Main Websites&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://scala-lang.org/&quot;&gt;scala-lang.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scala.epfl.ch/&quot;&gt;scala.epfl.ch (The Scala Center)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#api--reference-docs&quot; id=&quot;api--reference-docs&quot;&gt;API / Reference Docs&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://scala-lang.org/api/3.x/&quot;&gt;Scala 3 API docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.scala-lang.org/scala3/reference/&quot;&gt;Scala 3 reference docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dotty.epfl.ch/api/index.html&quot;&gt;Scala 3 nightly API docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dotty.epfl.ch/docs/reference/index.html&quot;&gt;Scala 3 nightly reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.scala-lang.org/files/archive/api/2.13.x/&quot;&gt;Scala 2.13 library API docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.scala-lang.org/api/2.13.x/scala-compiler/scala/&quot;&gt;Scala 2.13 compiler API docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.scala-lang.org/api/2.13.x/scala-reflect/scala/reflect/&quot;&gt;Scala 2.13 reflection API docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scala-lang.org/files/archive/spec/2.13/&quot;&gt;Scala 2.13 language specification docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#repos&quot; id=&quot;repos&quot;&gt;Repos&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lampepfl/dotty&quot;&gt;lampepfl/dotty (Scala 3 development, issues, discussions, feature requests, and community build)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lampepfl/scala3-reference-docs&quot;&gt;lampepfl/scala3-reference-docs (Scala 3 language reference documentation sources)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/scala&quot;&gt;scala/scala (Scala 2 development)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/scala-dev&quot;&gt;scala/scala-dev(Scala 2 team issues)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/scala-lang&quot;&gt;scala/scala-lang (Scala website sources)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/docs.scala-lang&quot;&gt;scala/docs.scala-lang (Scala documentation website sources)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/scala.epfl.ch&quot;&gt;scala/scala.epfl.ch (Scala Center website sources)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/bug&quot;&gt;scala/bug (Scala 2 bug reports)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/community-build&quot;&gt;scala/community-build (Scala 2 community build)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/improvement-proposals&quot;&gt;scala/improvement-proposals (Scala 3 SIP proposals)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/VirtusLab/community-build3&quot;&gt;VirtusLab/community-build3 (&amp;quot;Unmanaged&amp;quot; Scala 3 community build)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scalacenter/advisoryboard&quot;&gt;scalacenter/advisoryboard (Scala Center Advisory Board proposals)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/scala-links</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/scala-links</guid><pubDate>Fri, 21 Oct 2022 00:00:00 +0200</pubDate></item><item><title>Music of the Month - August 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - August 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - August 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---august-2022&quot; id=&quot;music-of-the-month---august-2022&quot;&gt;Music of the Month - August 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A very busy last month. I found myself gravitating toward a lot of my favorites,
but also enjoying some new albums I stumbled across. I hope you enjoy what I
have for you this month.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a while I listed to a ton of lofi. I've always enjoyed music without lyrics,
and lofi often fit my mood when I wanted something chill to code or relax to.
One of my favorite older lofi albums was the &lt;a href=&quot;https://tomppabeats.bandcamp.com/album/harbor-lp&quot;&gt;Harbor
LP&lt;/a&gt; by
&lt;a href=&quot;https://tomppabeats.bandcamp.com/&quot;&gt;tomppabeats&lt;/a&gt;. However, they haven't released
a new full-length album in years. This was part of the reason I was so excited
to see a new album from them drop this month. &lt;em&gt;Habits&lt;/em&gt; is the album of the month
for me this month.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; max-width: 350px; height: 753px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=3664607190/size=large/bgcol=333333/linkcol=ffffff/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://tomppabeats.bandcamp.com/album/habits&quot;&gt;Habits by tomppabeats&lt;/a&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Last month I listed a
&lt;a href=&quot;https://www.chris-kipp.io/blog/music-of-the-month---july-2022&quot;&gt;Moya&lt;/a&gt; by
&lt;a href=&quot;https://brainwashed.com/godspeed/&quot;&gt;Godspeed You! Black Emperor&lt;/a&gt; as my song of
the month. In part, that led me to listen to a ton more of GY!BE this past
month. In general, I think Efrim Menuck is just a genius. If you're a GY!BE fan
you may have also heard of &lt;a href=&quot;https://en.wikipedia.org/wiki/Thee_Silver_Mt._Zion_Memorial_Orchestra&quot;&gt;A Silver Mt.
Zion&lt;/a&gt;,
which is project of Menucks with some of the same members of GY!BE. The song &lt;em&gt;13
Angels Standing Guard 'Round The Side Of Your Bed&lt;/em&gt; is one of my favorites of
theirs and is the song of the month for me this month.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; max-width: 350px; height: 470px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=2227663860/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/track=3023925315/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://theesilvermtzion.bandcamp.com/album/he-has-left-us-alone-but-shafts-of-light-sometimes-grace-the-corner-of-our-rooms&quot;&gt;He Has Left Us Alone But Shafts Of Light Sometimes Grace The Corner Of Our Rooms... by Thee Silver Mt Zion Memorial Orchestra&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-august-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-august-2022</guid><pubDate>Wed, 31 Aug 2022 00:00:00 +0200</pubDate></item><item><title>Music of the Month - July 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - July 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - July 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---july-2022&quot; id=&quot;music-of-the-month---july-2022&quot;&gt;Music of the Month - July 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;So I've finally stepped away from Jazz a bit this past month probably because it
was a bit hectic for me. Typically when it's hectic I find myself floating back
to more familiar records that bring familiarity and comfort.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;During this past month I watched &lt;a href=&quot;https://www.imdb.com/title/tt4574334/&quot;&gt;Stranger
Things&lt;/a&gt; for the very first time, and
there are a handful of scenes that play &lt;a href=&quot;https://en.wikipedia.org/wiki/Jim_Croce&quot;&gt;Jim
Croce&lt;/a&gt;. Growing up my Dad listened to a
ton of &amp;quot;oldies&amp;quot; so a lot of these artists from the 60s and 70s remind me of good
ol days on the farm where I grew up working with my Dad. Therefore, the album of
the month for me is the classic &lt;em&gt;You Don't Mess Around With Jim&lt;/em&gt;.&lt;/p&gt;
&lt;div style=&quot;max-width:100%;&quot;&gt;&lt;div style=&quot;position:relative;padding-bottom:calc(56.25% + 52px);height: 0;&quot;&gt;&lt;iframe style=&quot;position:absolute;top:0;left:0;&quot; width=&quot;100%&quot; height=&quot;100%&quot; src=&quot;https://odesli.co/embed/?url=https%3A%2F%2Falbum.link%2Fi%2F1203475854&amp;theme=dark&quot; frameborder=&quot;0&quot; allowfullscreen sandbox=&quot;allow-same-origin allow-scripts allow-presentation allow-popups allow-popups-to-escape-sandbox&quot; allow=&quot;clipboard-read; clipboard-write&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It's hard to explain &lt;a href=&quot;https://brainwashed.com/godspeed/&quot;&gt;Godspeed You! Black
Emperor&lt;/a&gt;, GY!BE, to someone that's not familiar with
them. My &amp;quot;Music of the Month&amp;quot; series hasn't really shown it, but one of my
most-listened to genres is post-rock. Every since I discovered &lt;a href=&quot;http://www.explosionsinthesky.com/&quot;&gt;Explosions in
the Sky&lt;/a&gt; in high school, they genre has
meant a lot to me. GY!BE has some incredible albums. They're slow burners, so
some of them really take time to get into and maybe even more time to
appreciate. 20 minute songs are the norm. This month one that I've found myself
listening to over and over is Moya from the record &lt;em&gt;Slow Riot from New Zero
Kanada&lt;/em&gt;. Again, if you've never listened to them before, take a seat, make sure
to listen to the whole thing, and enjoy.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; max-width: 350px; height: 470px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=3803531554/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/track=1587484362/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://godspeedyoublackemperor.bandcamp.com/album/slow-riot-for-new-zero-kanada&quot;&gt;Slow Riot for New Zero Kanada by Godspeed You! Black Emperor&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-july-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-july-2022</guid><pubDate>Sun, 31 Jul 2022 00:00:00 +0200</pubDate></item><item><title>What I Learned in a Year of Podcasting</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;What I learned in the first year of starting a podcast, and commonly asked questions.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;What I Learned in a Year of Podcasting&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;What I learned in the first year of starting a podcast, and commonly asked questions.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;What I Learned in a Year of Podcasting&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#what-i-learned-in-the-first-year-of-starting-a-podcast-and-commonly-asked-questions&quot; id=&quot;what-i-learned-in-the-first-year-of-starting-a-podcast-and-commonly-asked-questions&quot;&gt;What I learned in the first year of starting a podcast, and commonly asked questions.&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;My hope is that this post will serve two purposes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Be a reflection on starting and doing a
&lt;a href=&quot;https://www.tooling-talks.com/&quot;&gt;podcast&lt;/a&gt; for a year on what I'd consider a
pretty niche topic, &lt;a href=&quot;https://scala-lang.org/&quot;&gt;Scala&lt;/a&gt; developer Tooling.&lt;/li&gt;
&lt;li&gt;Be a collection of answers to commonly asked questions I get about starting a
podcast, in hopes to help others on their journey.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Feel free to jump down to the section that interests you the most, or just keep
reading.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#how-do-you-pick-a-topic&quot;&gt;How do you pick a topic?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-do-you-even-make-a-podcast&quot;&gt;How do you even make a podcast?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#what-do-you-need&quot;&gt;What do you need?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#what-does-it-cost&quot;&gt;What does it cost?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-much-time-does-it-take&quot;&gt;How much time does it take?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-do-you-invite-guests&quot;&gt;How do you invite guests?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#how-do-you-control-quality&quot;&gt;How do you control quality?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#how-do-you-pick-a-topic&quot; id=&quot;how-do-you-pick-a-topic&quot;&gt;How do you pick a topic&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I first wanted to start a podcast, it was for a variety of reasons that
probably differ a bit per individual, but one of the main reasons for me was
that throughout Covid my interaction with people was almost entirely limited to
online activity. It was really during that time for me that I got even more
heavily involved in the Scala community, and starting really connecting with
people that I was interacting with on a daily basis, digitally. With that being
said, they were just drive-by interactions, short chats on Discord, or even just
&lt;em&gt;thumbs up&lt;/em&gt; on GitHib issues. I often heard others fondly talk about chatting
with friends at conferences, skipping a session and hacking on something
together, or just in general having conversations about the interests you share
that not many of your direct in-person circles do. I longed for that experience,
and thought maybe starting a podcast talking about something I deeply cared
about and spent an exuberant amount of time on was a good idea.&lt;/p&gt;
&lt;p&gt;The practical advice I pull out of that is that if you're going to start a
podcast, start it about something you care about. Start it about something you
don't mind spending hours of your weekend time researching about, or
re-listening to recorded episodes as you edit, or search around for new topics
to talk about. We'll get into this more later, but it also helps to be a part of
the community of the thing you're going to be speaking about. This helps
tremendously with finding new guests, hearing about new interesting topics you
can cover, and to help spread the word around about this new show that you're
putting effort into. You also want to make sure there is enough substance and
options to talk about in this topic. This is all made easier when you're
actively participating in that community.&lt;/p&gt;
&lt;p&gt;It's also worth mentioning that when thinking about your topic, you should also
put some thought into your audience. While it may seem obvious, if you pick a
more niche topic you need to know that you're working with a much smaller pool
of people that will probably engage in your podcast in a very different way.
Here are some things I considered when thinking about my potential audience when
I started &lt;a href=&quot;https://www.tooling-talks.com/&quot;&gt;Tooling Talks&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tooling is a more niche topic than Scala in general, so I'm already
limiting my audience compared to doing a more generic Scala podcast.&lt;/li&gt;
&lt;li&gt;Tooling is something most new developers don't think or really care that much
about if it's doing it's job. How can I make sure this podcast is also
something a newcomer to the ecosystem could listen to and gain from?&lt;/li&gt;
&lt;li&gt;Anecdotally, I see less male-identifying individuals in Scala tooling than
other ecosystems, how can I ensure my podcast reaches a more diverse
audience than if I otherwise wouldn't think about this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are still questions I often think about when I look at the trends in
listeners, the amount of listeners I have, or when inviting new guests.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#how-do-you-even-make-a-podcast&quot; id=&quot;how-do-you-even-make-a-podcast&quot;&gt;How do you even make a podcast&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thankfully, this is a topic pretty well covered out there, so I won't go too
much into the technical details, but will give an overview of some of the
choices I made here.&lt;/p&gt;
&lt;p&gt;I find that most of the confusion about a podcast comes from distribution, not
the actual &amp;quot;making&amp;quot; the podcast. Once you have a recording, that recording needs
to somehow be distributed to the various services that people listen to. Your
podcast is really just an &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_feed&quot;&gt;rss feed&lt;/a&gt;
that needs to be hosted so that people can subscribe to or that services like
Spotify or Deezer can subscribe to in order to distribute your podcast. Apart
form your actual feed you'd also need to actual host your audio files in a place
the feed can access. You'd also then need to go to every service you want to
distribute your podcast to and register your podcast with them so they know to
follow your rss feed. While you can do all this manually, it's not something I'd
recommend. There are plenty of services that offer to do all of this for you.
Apart from just hosting your actual podcast and rss feed, they also make it much
easier to distribute your podcast to other services, they offer embedded players
for your podcast episodes, and some even offer you a simple landing page to act
as a website. I personally use &lt;a href=&quot;https://www.buzzsprout.com/&quot;&gt;Buzzsprout&lt;/a&gt; for
this, which as been fantastic. I even have a
&lt;a href=&quot;https://tooling-talks.buzzsprout.com/&quot;&gt;website&lt;/a&gt; with them even though I never
link it, since I prefer my own minimal &lt;a href=&quot;https://www.tooling-talks.com/&quot;&gt;site&lt;/a&gt;.
There are a bunch of options here, and the search that you should be doing is
&amp;quot;what's the best place for me to host my podcast&amp;quot;. Since there's abundant
articles out there for this, I'll leave that research up to you, but instead
leave you with a few things I considered when I chose to use Buzzsprout.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What's the cost? Most of these do cost a bit of money, so what's an actual
affordable option.&lt;/li&gt;
&lt;li&gt;Which offers a flexible embedded player? Like I mentioned, I wanted to host my
own site, so this mattered to me.&lt;/li&gt;
&lt;li&gt;Which offered the easiest way to distribute my podcast to the various places,
like Spotify, Google podcasts, etc. If I could just click a button, that'd
be awesome.&lt;/li&gt;
&lt;li&gt;Which is commonly reviewed as one of the easiest to use? I don't want some
no-name cheaper alternative that ends up sucking.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#what-do-you-need&quot; id=&quot;what-do-you-need&quot;&gt;What do you need&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If we wanted to completely simplify this section, you could just say:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A way to record yourself and your guest&lt;/li&gt;
&lt;li&gt;A place to upload your recordings and your rss feed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, that's pretty radically over-simplified. I'll break down what I chose
to get and why.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#recording&quot; id=&quot;recording&quot;&gt;Recording&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Arguably the most important thing in making a podcast is audio quality. I have
an entire section on this down below, but this often translates to three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Getting a better microphone to connect to your recording device&lt;/li&gt;
&lt;li&gt;Using recording-specific software to help with noise reduction and quality&lt;/li&gt;
&lt;li&gt;Using some type of post-production software to edit your podcast&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Having an external microphone will almost always sound better than using your
built-in mic. I think there is some confusion here where people assume that this
is always very expensive, but there are mics in the under $100 range that will
&lt;em&gt;greatly&lt;/em&gt; improve your audio quality. The nice thing is that investing in a mic
here doesn't just need to be for your podcast, but can also benefit you on work
calls, recordings, or anything else you might want to use a mic for. I wouldn't
recommend going out right away and dropping a ton of money here, but getting an
affordable external mic here will help. Keep in mind that even with a nice mic,
a crappy environment or incorrect usage can totally negate any gains here.
Again, we'll talk about controlling quality down below.&lt;/p&gt;
&lt;p&gt;There are multiple ways to record your podcast. Some of the most obvious are
just to recording your Google Meet call, your Zoom audio, or whatever software
you're choosing to use. This is for sure the cheapest, but not the highest
quality. There is also software specifically made for recording which can do
things like recording on separate audio tracks or local buffering that can
really help the quality of your sound and ease post-production. I use
&lt;a href=&quot;https://squadcast.fm/&quot;&gt;Squadcast&lt;/a&gt; for this and have had a great experience.&lt;/p&gt;
&lt;p&gt;For editing I use &lt;a href=&quot;https://www.audacityteam.org/&quot;&gt;Audacity&lt;/a&gt;. There are a ton of
other more-advanced tools out there for this, but if you want a tool that can
normalize your audio, easily split and join clips, and make transitions with,
then Audacity is great. It's also open source and free, which was a nice bonus
for me. There is actually a ton of useful articles about using Audacity on the
&lt;a href=&quot;https://www.buzzsprout.com/blog/category/podcast-editing&quot;&gt;Buzzsprout blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#hosting-and-distributing&quot; id=&quot;hosting-and-distributing&quot;&gt;Hosting and Distributing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I already covered this a bit up above, but I host my podcast on
&lt;a href=&quot;https://www.buzzsprout.com/&quot;&gt;Buzzsprout&lt;/a&gt;. One of the main features I wanted was
to be able to embed an episode. Here's what that looks like for me:&lt;/p&gt;
&lt;div id=&quot;buzzsprout-player-10348256&quot;&gt;&lt;/div&gt;&lt;script src=&quot;https://www.buzzsprout.com/1830936/10348256-amina-adewusi-let-s-talk-about-newcomers.js?container_id=buzzsprout-player-10348256&amp;player=small&quot; type=&quot;text/javascript&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;I also wanted to be able to style it to match my website, which was pretty easy
to do. Since I wanted to host these, I also needed a website, which I was able
to &lt;a href=&quot;https://github.com/ckipp01/tooling-talks&quot;&gt;make myself&lt;/a&gt;(with Scala) and host
on &lt;a href=&quot;https://vercel.com/&quot;&gt;Vercel&lt;/a&gt;. Again, if you don't want to do this part, it's
much easier to just use the built-in website that many podcast hosting sites
will provide for you.&lt;/p&gt;
&lt;p&gt;Since I use Buzzsprout, distribution was also pretty easy. Most of them were
just button clicks, but there were a couple more that involved going to their
respective sites and registering, and sometimes submitting the feed manually. I
have my podcast being circulated on around 20 different site mostly thanks to
how easy the process was.&lt;/p&gt;
&lt;p&gt;I haven't mentioned it yet, but if you host your own site, you obviously need a
domain name. Here is also the part you could talk some about branding, using a
dedicated Twitter account, etc. I made some opinionated choices here to not
really brand it too separately from myself. Mainly because I didn't want to have
to deal with it feeling like a &amp;quot;brand&amp;quot; or separate project, but just an
extension of myself. This is why I don't have a separate dedicated Twitter
account for Tooling Talks, a separate way to support the show, and why the
branding on the site looks very similar to my personal site.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#what-does-it-cost&quot; id=&quot;what-does-it-cost&quot;&gt;What does it cost&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So this section can be radically different depending on how much you want to
invest in this. You could get by with a pretty minimal budget here if needed,
but to give you a real idea, I've outlined the setup and regular costs for
&lt;a href=&quot;https://www.tooling-talks.com/&quot;&gt;Tooling Talks&lt;/a&gt; down below.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#setup-costs&quot; id=&quot;setup-costs&quot;&gt;Setup costs&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Cost&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Item&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;$14.16&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Domain name and ICANN fee - Purchased for one year via &lt;a href=&quot;https://www.namecheap.com/&quot;&gt;Namecheap&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;$0&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Hosting on &lt;a href=&quot;https://vercel.com/&quot;&gt;Vercel&lt;/a&gt; - The free account is fine for this&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;$24.63&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Logo for podcast - Ordered via &lt;a href=&quot;https://www.fiverr.com/&quot;&gt;Fiverr&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;€255&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Creative Bundle (mic and interface) - Included a &lt;a href=&quot;https://rode.com/en/microphones/studio-condenser/nt1a&quot;&gt;RØDE NT1-A&lt;/a&gt; and a &lt;a href=&quot;https://focusrite.com/en/audio-interface/scarlett/scarlett-solo&quot;&gt;Focusrite Scarlett Solo&lt;/a&gt;. Both of these I can use for streaming and for work calls.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;€34.99&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;a href=&quot;https://www.tonormic.com/products/tonor-t20-mic-arm-stand&quot;&gt;Mic arm&lt;/a&gt; - Not necessary but depends on your mic and greatly helps with placement.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;a href=&quot;#monthly-costs&quot; id=&quot;monthly-costs&quot;&gt;Monthly costs&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Cost&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Item&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;€7&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Avatar for guest - Again, from the same artist on Fiverr. Price fluctuates a bit due to tipping.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;$12&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Monthly cost to host on Buzzsprout - They do have a free account, but it only keeps your episodes for 90 days.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;$20&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Monthly cost for Squadcast - This is the cheapest tier they have.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;One way to offset these costs is to provide a way for people to support the show
financially. This can be through &lt;a href=&quot;https://www.patreon.com/&quot;&gt;Patreon&lt;/a&gt;,
&lt;a href=&quot;https://ko-fi.com/&quot;&gt;ko-fi&lt;/a&gt;, or even &lt;a href=&quot;https://github.com/sponsors/ckipp01&quot;&gt;GitHub
Sponsors&lt;/a&gt;. I used ko-fi for a while, but
after about 3/4 of the year of having it I decided to just close it down. I had
gotten a couple one-off gifts there, but I also felt pressured to add regular
updates their to justify it. Since I didn't want to do that, I just opted for my
personal GitHub Sponsors page since that's where most of my activity can be seen
anyways.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#how-much-time-does-it-take&quot; id=&quot;how-much-time-does-it-take&quot;&gt;How much time does it take?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is arguably as important or more important than the actual monthly
financial cost to have a podcast. Depending on how much you value your time,
this is where the cost really comes in, and also why it's important that you
enjoy the topic you chose in the first place. While I've found that it can vary
a bit on the guest, the quality of the recording, etc, here is how I end up
breaking down the expected time an episode will take me. Note that some of these
are hard to quantify since some of my guests I know really well and it's just a
simple message, where others I've had to hunt down their contact info by
reaching out to mutual contacts, cold emailing, Twitter DMing, etc. The same
goes for preparing since if I'm going to interview someone about
&lt;a href=&quot;https://scalameta.org/metals/&quot;&gt;Metals&lt;/a&gt; there is way less prep needed than for
example when I spoke to &lt;a href=&quot;https://www.tooling-talks.com/episode-6&quot;&gt;Rebecca about
Unison&lt;/a&gt;. So consider all of the vales
down below as averages.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Time&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Activity&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1 Hour&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Figuring out how to contact and actually inviting, and fully planning the episode (average)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3 Hours&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Playing around with the project we'll talk about or doing research on it&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1.5 Hours&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;The actual recording of the episode&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2 Hours&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Editing the podcast. It typically takes me about 2 times the episode length to edit it&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;.5 Hour&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Writing the intro, update the website, communicating with the avatar artist&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;8 hours per episode is probably a pretty accurate estimate of the average time I
spend on a single episode. Originally, I was really surprised by this as I
didn't really expect an episode to take this long. It hopefully puts it into
perspective the amount of time that goes into making episodes. NOTE, my episodes
are &lt;em&gt;long&lt;/em&gt;. They don't need to be this long. If I'd aim for like a 45 min
average, this would shave off hours of time.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#how-do-you-invite-guests&quot; id=&quot;how-do-you-invite-guests&quot;&gt;How do you invite guests?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is something I still feel like I haven't nailed down. In theory the ideal
way this goes for me is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find someone I'd really like to do an episode with.&lt;/li&gt;
&lt;li&gt;Shoot a message to the person asking them to be on the show by
&lt;a href=&quot;https://www.tooling-talks.com/&quot;&gt;showing&lt;/a&gt; them who's been on previously, and
link the
&lt;a href=&quot;https://github.com/ckipp01/tooling-talks/blob/main/process.md&quot;&gt;process&lt;/a&gt; I
follow so they know what to expect.&lt;/li&gt;
&lt;li&gt;Once accepted I send them a &lt;a href=&quot;https://squadcast.fm/&quot;&gt;Squadcast&lt;/a&gt; invite so they
can actually put something on their calendar.&lt;/li&gt;
&lt;li&gt;About a week before we interview I'll send them a list of questions likely to
appear when we talk.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that's it. The fist step of identifying a guest in my opinion is pretty
easy, but the reaching them is not if they're outside of your network. The
guests I've had on the show that I've already interacted with in the past were
super easy to invite. I had their contact info, they recognized my name, and
they were quick to respond with a yes. Then we go one layer out from that and we
have people that I've never really spoken with, but we run in the same circles
so they at least recognized my name, or maybe even have heard of the show
before. Sometimes these were a bit harder since Twitter DMs are a terrible way
to get a hold of someone. Most people you try to contact this way won't even see
your message. The difficult part is that sometimes this is the only public way a
person has to contact them. Sometimes this an oversight, but sometimes is
intentional. This becomes extremely difficult when you're trying to reach
someone outside of your network and in a different ecosystem for example. There
are a handful of people I've had on my list to have on the show since I've
started, and some of them I've never been able to figure out how to contact, as
I don't even have a shared connection with them, and some of them have just
ghosted me even when I found emails or other forms of contact. I think it's
something you just sort of need to accept, that not everyone wants to be on a
podcast, and that's ok. If I find the person's contact info and they don't
respond, I give it a couple months and then try again. If they don't respond the
second time, I move on unless I come across a mutual contact that is willing to
make an introduction.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#how-do-you-control-quality&quot; id=&quot;how-do-you-control-quality&quot;&gt;How do you control quality?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is another one I'm still working on. Especially if you have guests that
aren't local to you and you're doing your interviews remotely, there isn't a lot
you can control on your guests end. With that being said, this is also a point
that shouldn't dissuade your guest from being a guest. I don't want to lose out
on a potentially great guest just because they don't have an external mic. There
are other tips to help their quality as well. Here are some tips that I &lt;a href=&quot;https://github.com/ckipp01/tooling-talks/blob/main/recording-tips.md&quot;&gt;send
ahead of
time&lt;/a&gt; to
my guests, and also some things I do to help maintain the quality of sound:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Investing in software like &lt;a href=&quot;https://squadcast.fm/&quot;&gt;Squadcast&lt;/a&gt;. Squadcast for
sure isn't cheap, but it offers a couple things that really help. Firstly,
it buffers locally before upload so even if the internet gets crappy you're
not losing sound quality. Secondly, the audio tracks are separated which
makes it easier to edit a ticking noise on one side or loud bangs or stuff
like that. Investing in some type of software that offers these things are a
good way to help your own post-production and also help your guest not
stress if they don't have a great internet connection.&lt;/li&gt;
&lt;li&gt;Make sure your guest is wearing headphones. This will help with cutting out
any echo that might be picked up from their speakers. Any headphones will
do. I personally use a pair of &lt;a href=&quot;https://north-america.beyerdynamic.com/dt-770-pro.html&quot;&gt;Beyerdynamic DT-770
Pros&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Encourage your guest to not sit in a giant empty room. If you're basically
sitting in an empty room, your sound will sound like it was recorded in a
box. Having stuff around you to dampen the sound really helps.&lt;/li&gt;
&lt;li&gt;Encourage your guest to be mindful of loud water bottles or things on their
desk. This is an important one that can kill post-production time by having
to go through and edit all these noises out. Metal water bottles with screw
caps, clicky pens, and all sorts of things someone could play with on their
desk can make noise and be picked up by their mic, especially if they are
using their built-in mic or a cheap external mic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope this all helps you on your podcast journey, or just gives you some
insight into mine. I've really enjoyed talking with all my guests, and have also
really learned to appreciate it when I hear quality podcasts since I know
first-hand the effort it takes. If you're considering making a podcast, but
still have unanswered questions, don't hesitate to reach out.&lt;/p&gt;
&lt;p&gt;Thanks for stopping by.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/what-i-learned-in-a-year-of-podcasting</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/what-i-learned-in-a-year-of-podcasting</guid><pubDate>Mon, 18 Jul 2022 00:00:00 +0200</pubDate></item><item><title>Music of the Month - June 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - June 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - June 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---june-2022&quot; id=&quot;music-of-the-month---june-2022&quot;&gt;Music of the Month - June 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I've been seemingly just living in the land of Jazz lately, because this month
will again be filled with some incredible Jazzyish tunes. I've also been
pleasantly surprised recently at the others in my tech circle that also seem to
enjoy jazz. This has been a fun intersection for me.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like last month, I made a new discovery this month that blew my mind. There was
&lt;a href=&quot;https://skulisverrisson.bandcamp.com/album/the-box-tree&quot;&gt;an album&lt;/a&gt; I came
across called &lt;em&gt;Box Tree&lt;/em&gt; by Skúli Sverrisson and Óskar Guðjónsson. While it's
not the focus of this month it's a beautiful album nonetheless, and it
introduced me to the playing of Óskar Guðjónsson. While exploring his work I was
introduced to &lt;a href=&quot;https://adhdiceland.com/&quot;&gt;ADHD&lt;/a&gt;, and was just blown away. Their
album &lt;em&gt;Adhd 7&lt;/em&gt; is the album of the month for me this month.&lt;/p&gt;
&lt;div style=&quot;max-width:100%;&quot;&gt;&lt;div style=&quot;position:relative;padding-bottom:calc(56.25% + 52px);height: 0;&quot;&gt;&lt;iframe style=&quot;position:absolute;top:0;left:0;&quot; width=&quot;100%&quot; height=&quot;100%&quot; src=&quot;https://odesli.co/embed/?url=https%3A%2F%2Falbum.link%2Fi%2F1454201228&amp;theme=dark&quot; frameborder=&quot;0&quot; allowfullscreen sandbox=&quot;allow-same-origin allow-scripts allow-presentation allow-popups allow-popups-to-escape-sandbox&quot; allow=&quot;clipboard-read; clipboard-write&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;They played a live show for KEXP that was also incredible. It's rare for me to
just sit and watch a recorded show in its entirety, but I did for this.&lt;/p&gt;
&lt;iframe max-width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/K5VItQCiTHo&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The song of the month for me comes off of the album &lt;em&gt;Box Tree&lt;/em&gt; I mentioned up
above by Skúli Sverrisson and Óskar Guðjónsson. The song is &lt;em&gt;For Seconds at a
Time&lt;/em&gt;, which is the first one I heard off the album. Starts off real simple, and
then just coddles you into relaxation when the Sax comes in. Such a beautiful
song.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 350px; height: 470px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=2258369861/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/track=2778140934/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://skulisverrisson.bandcamp.com/album/the-box-tree&quot;&gt;The Box Tree by Skúli Sverrisson, Óskar Guðjónsson&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-june-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-june-2022</guid><pubDate>Mon, 4 Jul 2022 00:00:00 +0200</pubDate></item><item><title>Music of the Month - May 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - May 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - May 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---may-2022&quot; id=&quot;music-of-the-month---may-2022&quot;&gt;Music of the Month - May 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This month was one of those months were you find a new piece of music and you
basically listen to it the entire month. &lt;a href=&quot;https://vegatrails.com/&quot;&gt;Vega Trails&lt;/a&gt;,
I love you.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I discovered this duo this month by total happen chance. &lt;a href=&quot;https://vegatrails.com/&quot;&gt;Vega
Trails&lt;/a&gt; is made up of double-bassist and composer Milo
Fitzpatrick from &lt;a href=&quot;https://www.porticoquartet.com/&quot;&gt;Portico Quartet&lt;/a&gt; and Jordan
Smart from &lt;a href=&quot;http://mammalhands.com/&quot;&gt;Mammal Hands&lt;/a&gt;. The music these two have
made in their first album together, &lt;em&gt;Tremors in the Static&lt;/em&gt;, is simply magical.
I haven't been touched this much by an album in quite some time. The recording
if phenomenal, the pieces are passionate, and the range and depth that is
created just by the two of them surprises me every time I listen.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 350px; height: 753px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=3552507018/size=large/bgcol=333333/linkcol=ffffff/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://vegatrails.bandcamp.com/album/tremors-in-the-static&quot;&gt;Tremors in the Static by Vega Trails&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;They did a filming of this in St Thomas's Church in London, and it equally as
fantastic. Here is a video showing a few of the songs.&lt;/p&gt;
&lt;iframe max-width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/P9urWc1yJ_Q&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A song that specifically stuck out to me this month was &lt;em&gt;Moving by Backwards&lt;/em&gt; by
SALES. I've been a fan of SALES for a while now and this is their latest single
they've recently put out. It's just good indie dreamy pop.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 350px; height: 442px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/track=2388693841/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://sales.bandcamp.com/track/moving-by-backwards&quot;&gt;Moving by Backwards by SALES&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-may-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-may-2022</guid><pubDate>Fri, 3 Jun 2022 00:00:00 +0200</pubDate></item><item><title>Music of the Month - April 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - April 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - April 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---april-2022&quot; id=&quot;music-of-the-month---april-2022&quot;&gt;Music of the Month - April 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I missed March for no other reason than I just forgot about it. However, this
past month I had a few weeks off, and spent a good amount of time relaxing,
thinking about the future, and of course listening a to a lot of music. I found
my music all over the place this month, so I honestly could have picked a number
of albums and songs. However, here are a couple that stood out to me this month.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I listen to a fair amount of classical and 4 Seasons by Vivaldi has always been
a favorite of mine as cliche as it sounds. It was one of my favorite live
performances I've seen when visiting Paris in Saint-Chapelle, and the rendition
I'm about to suggest is just incredible. Max Richter is such a gifted composer
and beautiful human being. This past month was the ten year anniversary of his
&lt;em&gt;Recomposed by Max Richter: Vivaldi - The Four Seasons&lt;/em&gt;, which is my album of
the month this month.&lt;/p&gt;
&lt;iframe max-width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/Ebm69gW9UlI&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Due to it being the anniversary he released some new videos of the performance
which are fantastic.&lt;/p&gt;
&lt;iframe max-width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/6T0MFCX9SLI&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started to listen to quite a bit of lofi when I started coding, and then led
to just me listing to a lot of lofi. I've found a lot of interesting artists out
there, but I find Mishaal really interesting since in his songs you'll find a
mix of Spanish, Arabic, and English due to his background. The song of the month
for me this month is &lt;em&gt;Kid Goku&lt;/em&gt; by Mishaal and Masked Man.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 350px; height: 442px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/track=1606381239/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://collegemusicrecords.bandcamp.com/track/kid-goku&quot;&gt;Kid Goku by Mishaal&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month... unless I forget again&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-april-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-april-2022</guid><pubDate>Fri, 6 May 2022 00:00:00 +0200</pubDate></item><item><title>The Debug Adapter Protocol and Scala</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;A description of how the Debug Adapter Protocol works with Metals and Scala.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;The Debug Adapter Protocol and Scala&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;A description of how the Debug Adapter Protocol works with Metals and Scala.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;The Debug Adapter Protocol and Scala&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#the-debug-adapter-protocol-and-scala-how-it-all-works-together&quot; id=&quot;the-debug-adapter-protocol-and-scala-how-it-all-works-together&quot;&gt;The Debug Adapter Protocol and Scala. How it all works together.&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;In the last couple years in Scala we've seen a surge in usage of various tooling
protocols. A growing number of developers are at least familiar with the
Language Server Protocol (LSP), especially if you're a
&lt;a href=&quot;https://scalameta.org/metals/&quot;&gt;Metals&lt;/a&gt; user or were an early user of Dotty when
it had a built-in LSP server. You've also more than likely been exposed to the
Build Server Protocol (BSP) even if it was just in passing by seeing
&lt;a href=&quot;https://www.scala-sbt.org/&quot;&gt;sbt&lt;/a&gt; create a &lt;code&gt;.bsp/&lt;/code&gt; directory in your Scala
workspace. Another popular protocol is the Debug Adapter Protocol, which again
you may have rubbed up against if you're using Metals. I've seen a lot of
questions about the how DAP works with Metals and this one actually has a few
more moving parts than the others to make it all work together. So, I wanted to
jot down some notes both to ensure I understand all the moving parts, to serve
as a detailed explanation of sorts of how it all works together, and to
hopefully help you to get the full picture as well.&lt;/p&gt;
&lt;p&gt;Keep in mind that this will differ a bit per client. Client here may also be a
bit ambiguous since we'll be talking about Metals client extensions and DAP
clients, so I'll try to always differentiate them by saying &amp;quot;Metals client&amp;quot; or
&amp;quot;DAP client&amp;quot; &lt;em&gt;even though they may be the same thing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Also keep in mind that this won't really be a technical explanation of &lt;em&gt;how&lt;/em&gt;
things like expressions evaluation or breakpoints actually work, but more of an
overview of all the moving pieces to better understand what happens between all
of your tools when you trigger a &lt;code&gt;run&lt;/code&gt; or &lt;code&gt;debug&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#what-is-dap&quot; id=&quot;what-is-dap&quot;&gt;What is DAP&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you're familiar with the goals of the LSP, then you're already familiar with
some of the goals of DAP. Basically, don't re-implement all the debugger
functionality over and over for every new language and tool that wants to
implement debugging. Taken from the &lt;a href=&quot;https://microsoft.github.io/debug-adapter-protocol/&quot;&gt;DAP
website&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Adding a debugger for a new language to an IDE or editor is not only a
significant effort, but it is also frustrating that this effort can not be
easily amortized over multiple development tools, as each tool uses different
APIs for implementing the same feature.
The idea behind the Debug Adapter Protocol (DAP) is to abstract the way how
the debugging support of development tools communicates with debuggers or
runtimes into a protocol. Since it is unrealistic to assume that existing
debuggers or runtimes adopt this protocol any time soon, we rather assume that
an intermediary component - a so called Debug Adapter - adapts an existing
debugger or runtime to the Debug Adapter Protocol.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The Debug Adapter Protocol makes it possible to implement a generic debugger
for a development tool that can communicate with different debuggers via Debug
Adapters. And Debug Adapters can be re-used across multiple development tools
which significantly reduces the effort to support a new debugger in different
tools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This description also brings up an important point of the actual Debug Adapter
since in many languages that do have existing debugger interfaces built right
into the language or platform, like the &lt;a href=&quot;https://docs.oracle.com/en/java/javase/17/docs/api/jdk.jdi/module-summary.html&quot;&gt;Java Debug
Interface&lt;/a&gt;,
which is what &lt;a href=&quot;https://github.com/microsoft/java-debug&quot;&gt;java-debug&lt;/a&gt; uses, which
is what
&lt;a href=&quot;https://github.com/scalacenter/scala-debug-adapter&quot;&gt;scala-debug-adapter&lt;/a&gt; uses,
which is what most build servers will be using, which is what Metals connects
to... you get the idea. However, we'll get further into that down below. All
that to say, the goal is that in a situation where a X editor user wants to
transition to another editor, &lt;em&gt;like Neovim&lt;/em&gt;, they can still have the same basic
debugging experience as long as that new client has a DAP client implementation
either built in or available as a plugin. The same can be said for the server
side if a Metals users decides to use Bloop as their build server or sbt as
their build server, they can share a common implementation of the server side of
the protocol and not have to fully re-implement it twice. The server side
example is exactly what the
&lt;a href=&quot;https://github.com/scalacenter/scala-debug-adapter&quot;&gt;scala-debug-adapter&lt;/a&gt; is
for.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#two-different-types-of-clients&quot; id=&quot;two-different-types-of-clients&quot;&gt;Two different types of clients&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I mentioned it up above, but also want to reiterate it here. Different language
server clients may handle the DAP client part differently. Like in the case of
VS Code, the DAP client is straight up just included in the editor. You can see
an example of this in the
&lt;a href=&quot;https://github.com/scalameta/metals-vscode/blob/main/src/scalaDebugger.ts&quot;&gt;scalameta/metals-vscode&lt;/a&gt;
extension. Notice that the imports are coming right from &lt;code&gt;vscode&lt;/code&gt;. This offers
an extremely tight integration that is totally abstracted away for the user.
There are other things that can then be built on top of that like the VS Code
Test Explorer API that &lt;a href=&quot;https://scalameta.org/metals/blog/2022/01/12/aluminium#support-test-explorer-api&quot;&gt;Metals recently added support
for&lt;/a&gt;.
Under the hood, the communication for these are still going through DAP. I like
to think of these as &amp;quot;extensions&amp;quot; to DAP similar to LSP extensions that aren't
necessarily part of the protocol, but follow the same pattern and even re-use
parts that &lt;em&gt;are&lt;/em&gt; part of the protocol. These then require more work for other
clients to implement, and they aren't expected to work out of the box like other
DAP features. For now, we won't focus on any of these, and we'll just stick to
the core DAP features.&lt;/p&gt;
&lt;p&gt;The other way this can look in a client is when your language server client
doesn't natively implement a DAP client, but has you use an extension to
implement this. You can see an example of this in
&lt;a href=&quot;https://github.com/scalameta/nvim-metals/blob/32a37ce2f2cdafd0f1c5a44bcf748dae6867c982/lua/metals/setup.lua#L109-L168&quot;&gt;scalameta/nvim-metals&lt;/a&gt;.
Notice that in the &lt;code&gt;setup_dap&lt;/code&gt; function the first thing we actually do is
require &lt;a href=&quot;https://github.com/mfussenegger/nvim-dap&quot;&gt;mfussenegger/nvim-dap&lt;/a&gt; which
is a fantastic plugin that implements the client portion of the protocol for
Neovim.&lt;/p&gt;
&lt;p&gt;So, whether you're using VS Code with a built-in DAP client integration or using
Neovim and a plugin like &lt;code&gt;nvim-dap&lt;/code&gt;, the core client functionality should be
largely the same. Moving forward all of examples will assume the second setup
using &lt;code&gt;nvim-dap&lt;/code&gt;, since that's what I'm most familiar with.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#how-does-everything-get-set-up&quot; id=&quot;how-does-everything-get-set-up&quot;&gt;How does everything get set up&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I don't want this to necessarily be a &amp;quot;getting started with &lt;code&gt;nvim-dap&lt;/code&gt;&amp;quot; guide,
as there are already guides out there, plus the
&lt;a href=&quot;https://github.com/mfussenegger/nvim-dap/blob/master/doc/dap.txt&quot;&gt;docs&lt;/a&gt; for
&lt;code&gt;nvim-dap&lt;/code&gt; are pretty detailed. Instead, I want to focus on &lt;em&gt;how&lt;/em&gt; this all works
together specifically when using &lt;code&gt;nvim-metals&lt;/code&gt;. Most of this will be
transferable to other Metals extensions that support DAP as well.&lt;/p&gt;
&lt;p&gt;Let's start with a simple piece of code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;@main def dapExample() =
  println(&amp;quot;hello people interested in DAP&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have &lt;code&gt;nvim-dap&lt;/code&gt; installed and you open a Scala project with a main method
you should see code lenses appear on your main method. In &lt;code&gt;nvim-metals&lt;/code&gt; it will
look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/code-lens.png&quot; alt=&quot;some scala code with code lens&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The first question we need to answer is &amp;quot;how did these get here?&amp;quot; and then &amp;quot;how
does this actually trigger a run or debug of my code?&amp;quot;. Behind the scenes what
actually happens is that Metals will have communicated with your build server
and gotten any main methods in your build target via a
&lt;a href=&quot;https://build-server-protocol.github.io/docs/extensions/scala.html#scala-main-classes-request&quot;&gt;&lt;code&gt;buildTarget/scalaMainClasses&lt;/code&gt;&lt;/a&gt;
request and cached those results. Then when the LSP request comes to Metals for
the &lt;code&gt;textDocument/codeLens&lt;/code&gt; Metals looks through the
&lt;a href=&quot;https://scalameta.org/docs/semanticdb/guide.html&quot;&gt;SemanticDB&lt;/a&gt; for the current
document and looks for any main methods. If it finds them, it compares them to
the cached ones that were retrieved earlier, and then creates code lenses for
them with special commands attached to them.&lt;/p&gt;
&lt;p&gt;Here are some illustrations of the above:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Example of the &lt;code&gt;buildTarget/scalaMainClasses&lt;/code&gt; request and response with the build server&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[Trace - 10:50:29 AM] Sending request 'buildTarget/scalaMainClasses - (7)'
Params: {
  &amp;quot;targets&amp;quot;: [
    {
      &amp;quot;uri&amp;quot;: &amp;quot;file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/test/?id\u003dSanity.test&amp;quot;
    },
    {
      &amp;quot;uri&amp;quot;: &amp;quot;file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/?id\u003dSanity&amp;quot;
    }
  ]
}

[Trace - 10:50:29 AM] Received response 'buildTarget/scalaMainClasses - (7)' in 6ms
Result: {
  &amp;quot;items&amp;quot;: [
    {
      &amp;quot;target&amp;quot;: {
        &amp;quot;uri&amp;quot;: &amp;quot;file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/?id\u003dSanity&amp;quot;
      },
      &amp;quot;classes&amp;quot;: [
        {
          &amp;quot;class&amp;quot;: &amp;quot;dapExample&amp;quot;,
          &amp;quot;arguments&amp;quot;: [],
          &amp;quot;jvmOptions&amp;quot;: [],
          &amp;quot;environmentVariables&amp;quot;: []
        }
      ]
    },
    {
      &amp;quot;target&amp;quot;: {
        &amp;quot;uri&amp;quot;: &amp;quot;file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/test/?id\u003dSanity.test&amp;quot;
      },
      &amp;quot;classes&amp;quot;: []
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Example of what the SemanticDB for our code snippet looks like. Notice the
first occurrence which is &lt;code&gt;scala/main#&lt;/code&gt;. Once found we'd get the symbol for that
occurrence and then check it against what was returned above.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-proto&quot;&gt;Sanity/src/example/Hello.scala
------------------------------

Summary:
Schema =&amp;gt; SemanticDB v4
Uri =&amp;gt; Sanity/src/example/Hello.scala
Text =&amp;gt; empty
Language =&amp;gt; Scala
Symbols =&amp;gt; 3 entries
Occurrences =&amp;gt; 3 entries

Symbols:
_empty_/Hello$package. =&amp;gt; final package object _empty_ extends Object { self: _empty_.type =&amp;gt; +2 decls }
_empty_/Hello$package.dapExample(). =&amp;gt; @main method dapExample(): Unit
_empty_/dapExample# =&amp;gt; final class dapExample extends Object { self: dapExample =&amp;gt; +2 decls }

Occurrences:
[0:1..0:5) =&amp;gt; scala/main#
[0:10..0:20) &amp;lt;= _empty_/Hello$package.dapExample().
[1:2..1:9) =&amp;gt; scala/Predef.println(+1).

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Example of the code lens request and response.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[Trace - 07:22:25 PM] Received request 'textDocument/codeLens - (92)'
Params: {
  &amp;quot;textDocument&amp;quot;: {
    &amp;quot;uri&amp;quot;: &amp;quot;file:///Users/ckipp/Documents/scala-workspace/sanity/Sanity/src/example/Hello.scala&amp;quot;
  }
}

[Trace - 07:22:25 PM] Sending response 'textDocument/codeLens - (92)'. Processing request took 1ms
Result: [
  {
    &amp;quot;range&amp;quot;: {
      &amp;quot;start&amp;quot;: {
        &amp;quot;line&amp;quot;: 0,
        &amp;quot;character&amp;quot;: 1
      },
      &amp;quot;end&amp;quot;: {
        &amp;quot;line&amp;quot;: 0,
        &amp;quot;character&amp;quot;: 5
      }
    },
    &amp;quot;command&amp;quot;: {
      &amp;quot;title&amp;quot;: &amp;quot;run&amp;quot;,
      &amp;quot;command&amp;quot;: &amp;quot;metals-run-session-start&amp;quot;,
      &amp;quot;arguments&amp;quot;: [
        {
          &amp;quot;targets&amp;quot;: [
            {
              &amp;quot;uri&amp;quot;: &amp;quot;file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/?id\u003dSanity&amp;quot;
            }
          ],
          &amp;quot;dataKind&amp;quot;: &amp;quot;scala-main-class&amp;quot;,
          &amp;quot;data&amp;quot;: {
            &amp;quot;class&amp;quot;: &amp;quot;dapExample&amp;quot;,
            &amp;quot;arguments&amp;quot;: [],
            &amp;quot;jvmOptions&amp;quot;: [],
            &amp;quot;environmentVariables&amp;quot;: []
          }
        }
      ]
    }
  },
  {
    &amp;quot;range&amp;quot;: {...},
    &amp;quot;command&amp;quot;: {
      &amp;quot;title&amp;quot;: &amp;quot;debug&amp;quot;,
      &amp;quot;command&amp;quot;: &amp;quot;metals-debug-session-start&amp;quot;,
      &amp;quot;arguments&amp;quot;: [...]
    }
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the above is to generate the &lt;code&gt;run&lt;/code&gt; and &lt;code&gt;debug&lt;/code&gt; code lenses, more or less
the same process happens for the &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;test-debug&lt;/code&gt; lenses as well. The
commands that are attached to the code lenses are LSP client commands that need
to be implemented by the client. As you can probably guess, the two commands
&lt;code&gt;metals-run-session-start&lt;/code&gt; starts just a normal run and a
&lt;code&gt;metals-debug-session-start&lt;/code&gt; starts a debug session. How that's done however
differs a bit by client. Since we're focusing on &lt;code&gt;nvim-metals&lt;/code&gt; and &lt;code&gt;nvim-dap&lt;/code&gt;
I'll outline a bit of what is happening behind the scenes to tie everything
together.&lt;/p&gt;
&lt;p&gt;Both of the commands will end up calling this function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lua&quot;&gt;local function debug_start_command(no_debug)
  return function(cmd, _)
    dap.run({
      type = &amp;quot;scala&amp;quot;,
      request = &amp;quot;launch&amp;quot;,
      name = &amp;quot;from_lens&amp;quot;,
      noDebug = no_debug,
      metals = cmd.arguments,
    })
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;nvim-dap&lt;/code&gt; there are two main concepts around configuration that are
important to grasp. The first is the adapter configuration. This is a table
given to &lt;code&gt;nvim-dap&lt;/code&gt; per language (although the keys are actually arbitrary, just
think of it per language) that basically tells &lt;code&gt;nvim-dap&lt;/code&gt; if it should launch a
debug adapter and if so how, or if it should connect to a running debugger and
if so where. This configuration can be a table with these details or a function
that takes a callback and a configuration. In the case of &lt;code&gt;nvim-metals&lt;/code&gt; we use
the latter with the callback, which will be explained further below. The second
configuration that is relevant here is the debuggee configuration which is the
configuration for your application you'll be debugging. So if you're familiar
with VS Code think of this as your &lt;code&gt;launch.json&lt;/code&gt;. &lt;code&gt;nvim-dap&lt;/code&gt; can actually work
using a &lt;code&gt;launch.json&lt;/code&gt;, but we won't focus on that here.&lt;/p&gt;
&lt;p&gt;So in the above &lt;code&gt;debug_start_command&lt;/code&gt; function the table being passed into
&lt;code&gt;dap.run()&lt;/code&gt; is your partially your debuggee configuration. The &lt;code&gt;type&lt;/code&gt; is a
reference to the adapter entry that matches this key, the &lt;code&gt;request&lt;/code&gt; is either
&lt;code&gt;attach&lt;/code&gt; or &lt;code&gt;launch&lt;/code&gt; indicating whether the debug-adapter should launch or
attach to a debuggee, the &lt;code&gt;name&lt;/code&gt; is a human readable name for the configuration
(which we'll revisit), and the &lt;code&gt;noDebug&lt;/code&gt; is whether or not debug mode should be
enabled. If this is &lt;code&gt;true&lt;/code&gt; breakpoints will be ignored. Finally, the &lt;code&gt;metals&lt;/code&gt;
key isn't part of the spec here and will actually be removed before being passed
to &lt;code&gt;nvim-dap&lt;/code&gt; when it fully becomes the debuggee configuration. However we use
it to be able to forward the arguments from the code lens to the adapter
configuration.&lt;/p&gt;
&lt;p&gt;So where is the adapter configuration? &lt;code&gt;nvim-metals&lt;/code&gt; fully handles the adapter
configuration for you. The main reason for this is that before we can actually
launch everything we need some information from Metals about how to connect to
the debugger. In order to get this, we again utilize LSP to get this information
before we actually start any DAP communication. Keep in mind we've already dove
into quite a few things, but no DAP communication has even started yet. This is
where the callback being part of the adapter configuration comes into play. When
we setup the adapter configuration it looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lua&quot;&gt;dap.adapters.scala = function(callback, config)
  local uri = vim.uri_from_bufnr(0)
  local arguments = {}

  if config.name == &amp;quot;from_lens&amp;quot; then
    arguments = config.metals
  else
    local metals_dap_settings = config.metals or {}

    arguments = {
      path = uri,
      runType = metals_dap_settings.runType or &amp;quot;run&amp;quot;,
      args = metals_dap_settings.args,
      jvmOptions = metals_dap_settings.jvmOptions,
      env = metals_dap_settings.env,
      envFile = metals_dap_settings.envFile,
    }
  end

  execute_command({
    command = &amp;quot;metals.debug-adapter-start&amp;quot;,
    arguments = arguments,
  }, function(_, _, res)
    if res then
      local port = util.split_on(res.uri, &amp;quot;:&amp;quot;)[3]

      callback({
        type = &amp;quot;server&amp;quot;,
        host = &amp;quot;127.0.0.1&amp;quot;,
        port = port,
        enrich_config = function(_config, on_config)
          local final_config = vim.deepcopy(_config)
          final_config.metals = nil
          on_config(final_config)
        end,
      })
    end
  end)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's walk through this. We first check the &lt;code&gt;config.name&lt;/code&gt; and if it's
&lt;code&gt;from_lens&lt;/code&gt; we know that this whole process was started from triggering a code
lens, so we grab everything in the &lt;code&gt;metals&lt;/code&gt; key and set that to &lt;code&gt;arguments&lt;/code&gt;
which will actually be sent to metals along with the
&lt;code&gt;metals.debug-adapter-start&lt;/code&gt; command. Let's ignore the &lt;code&gt;else&lt;/code&gt; branch if the name
isn't &lt;code&gt;from_lens&lt;/code&gt; since we're focusing on the code lens example here. The
&lt;code&gt;execute_command&lt;/code&gt; function will send the LSP command to Metals which will then
over BSP tell the build server to start the debug server. Your debug server is
(or if it's not it should be) using
&lt;a href=&quot;https://github.com/scalacenter/scala-debug-adapter&quot;&gt;scalacenter/scala-debug-adapter&lt;/a&gt;
to start and manage the debug server. Then the &lt;code&gt;res&lt;/code&gt; that is returned via BSP
will have the information we need, mainly the &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt; of the server
that has already been started. This is then forwarded over LSP back to
&lt;code&gt;nvim-metals&lt;/code&gt;. The &lt;code&gt;type&lt;/code&gt; here is now set to &lt;code&gt;server&lt;/code&gt; since there is already a
debugger running that we just want to connect to. The &lt;code&gt;enrich_config&lt;/code&gt; takes in
the debuggee configuration that we created before and strips the &lt;code&gt;metals&lt;/code&gt; key
out since it's no longer relevant for the actual run, and not part of DAP.&lt;/p&gt;
&lt;p&gt;At this point is when DAP communication actually starts. However, there is a
fair amount of stuff that has already happened. To recap all of this, here is a
diagram showing what we've all covered.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/dap-setup.svg&quot; alt=&quot;all communication between nvim-metals, nvim-dap, and metals&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-actual-dap-communication&quot; id=&quot;the-actual-dap-communication&quot;&gt;The actual DAP communication&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So after all of the set up above happens you're ready to actual have some DAP
communication. At this point in &lt;code&gt;nvim-metals&lt;/code&gt; &lt;code&gt;nvim-dap&lt;/code&gt; pretty much fully takes
over on the client side and communicates directly with the running debugger
which was started by your build server. Given the code we had earlier the
general flow of DAP communication isn't that interesting since we don't have any
breakpoints set, we aren't doing expression evaluation, conditionals etc. To
outline what the communication looks like between DAP client and DAP server,
here is another diagram.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/dap-communication.svg&quot; alt=&quot;dap communication&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While the diagram above isn't actually exhaustive it contains the main events
that get the point across of how the DAP server and client are communicating.
The communication here can also become much more complicated when start adding
in breakpoints. For example, let's pretend our code is slightly different than
what we had above with a single breakpoint set:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;@main def dapExample() =
  val greeting = &amp;quot;hello people interested in DAP&amp;quot;
  println(greeting) // breakpoint set on this line
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we set the breakpoint above where mentioned, it will pause at that point. In
&lt;code&gt;nvim-dap&lt;/code&gt; you can execute a &lt;code&gt;.scopes&lt;/code&gt; command in the debug REPL and you'll see
the variables in scope returned. The communication between the DAP server and
client for this looks like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[Trace][03:21:33 PM] Sent request:
{
  &amp;quot;type&amp;quot;: &amp;quot;request&amp;quot;,
  &amp;quot;seq&amp;quot;: 9,
  &amp;quot;command&amp;quot;: &amp;quot;variables&amp;quot;,
  &amp;quot;arguments&amp;quot;: {
    &amp;quot;variablesReference&amp;quot;: 3
  }
}
[Trace][03:21:33 PM] Received response:
{
  &amp;quot;type&amp;quot;: &amp;quot;response&amp;quot;,
  &amp;quot;seq&amp;quot;: 14,
  &amp;quot;request_seq&amp;quot;: 9,
  &amp;quot;command&amp;quot;: &amp;quot;variables&amp;quot;,
  &amp;quot;success&amp;quot;: true,
  &amp;quot;body&amp;quot;: {
    &amp;quot;variables&amp;quot;: [
      {
        &amp;quot;name&amp;quot;: &amp;quot;greeting&amp;quot;,
        &amp;quot;value&amp;quot;: &amp;quot;\&amp;quot;hello people interested in DAP\&amp;quot;&amp;quot;,
        &amp;quot;type&amp;quot;: &amp;quot;String&amp;quot;,
        &amp;quot;variablesReference&amp;quot;: 4,
        &amp;quot;namedVariables&amp;quot;: 0,
        &amp;quot;indexedVariables&amp;quot;: 0
      },
      {
        &amp;quot;name&amp;quot;: &amp;quot;this&amp;quot;,
        &amp;quot;value&amp;quot;: &amp;quot;Hello$package$@183&amp;quot;,
        &amp;quot;type&amp;quot;: &amp;quot;Hello$package$&amp;quot;,
        &amp;quot;variablesReference&amp;quot;: 5,
        &amp;quot;namedVariables&amp;quot;: 0,
        &amp;quot;indexedVariables&amp;quot;: 0
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see all sorts of examples of the communication that can take place if
you look through the types of events in the &lt;a href=&quot;https://microsoft.github.io/debug-adapter-protocol/specification&quot;&gt;specification
documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#without-code-lenses&quot; id=&quot;without-code-lenses&quot;&gt;Without code lenses&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So let's revisit the function from up above that we gave to
&lt;code&gt;dap.adapters.scala&lt;/code&gt; and now take a look at the &lt;code&gt;else&lt;/code&gt; branch that we ignored
before. One thing you may have noticed with the code lens is that it's
fully handled by &lt;code&gt;nvim-metals&lt;/code&gt; without really a great way to maybe set some
arguments that you'd like to pass into your &lt;code&gt;run&lt;/code&gt; or maybe some specific
jvmOptions. The recommended way to do this with &lt;code&gt;nvim-metals&lt;/code&gt; is to pre-define
your debuggee configuration. Let's say you wanted to trigger a run with a
specific argument and also a specific env file. You could defined a
configuration like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lua&quot;&gt;dap.configurations.scala = {
  {
    type = &amp;quot;scala&amp;quot;,
    request = &amp;quot;launch&amp;quot;,
    name = &amp;quot;Run with arg and env file&amp;quot;,
    metals = {
      runType = &amp;quot;runOrTestFile&amp;quot;,
      args = { &amp;quot;myArg&amp;quot; },
      envFile = &amp;quot;path/to/.env&amp;quot;,
    },
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NOTE: again, some of this is &lt;code&gt;nvim-dap&lt;/code&gt; specific, so if you're trying to follow
along, make sure to read through the
&lt;a href=&quot;https://github.com/mfussenegger/nvim-dap/blob/master/doc/dap.txt&quot;&gt;docs&lt;/a&gt; and
also take a look at the full &lt;a href=&quot;https://github.com/scalameta/nvim-metals/discussions/39&quot;&gt;example
configuration&lt;/a&gt; for
&lt;code&gt;nvim-metals&lt;/code&gt; users.&lt;/p&gt;
&lt;p&gt;So remembering the function from up above, the &lt;code&gt;metals&lt;/code&gt; key here won't actually
get passed into &lt;code&gt;nvim-dap&lt;/code&gt; at all, but is instead used to send the correct stuff
to Metals to ensure the debug server is set up correctly and the correct things
like args are taken into account. So now when you'd trigger a &lt;code&gt;dap.continue()&lt;/code&gt;
(it's called continue but will start a session if none exists) you'll see this
configuration as an option to use to start the process under &lt;code&gt;Run with arg and env file&lt;/code&gt;. If you have 3 different ones defined, maybe one with args, one
without, and one specifically for tests, you'd see those three to choose from.
Here is an example of what mine looks like locally when I trigger it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/run-configurations.png&quot; alt=&quot;example of my run configurations&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The communication here is slightly different than when using the code lens since
at this point we're triggering the process to start, but we don't &lt;em&gt;actually&lt;/em&gt;
know for sure if we're in a file that even has a main method. So some of the
same steps from above happen, but instead the order is a bit different. The
&lt;code&gt;runType&lt;/code&gt; key here is special functionality in Metals to search the current
document you're in to see if there are any many methods or tests that it can
run. If there is, it then goes ahead and runs it, or returns you a picker to
select which you'd like if there are multiple mains or test suites to choose
from. Here is a diagram showing the changed order of things when you trigger a
&lt;code&gt;run&lt;/code&gt; this way.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/debug-discovery.svg&quot; alt=&quot;debug discover communication&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#im-amazed-this-all-works&quot; id=&quot;im-amazed-this-all-works&quot;&gt;I'm amazed this all works&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even without going into the technical details of how each part of this works,
it's incredible that it even does with the amount of moving parts, different
protocols involved, and different tools being utilized. It speaks to the amount
of work that goes on by many different people working in different projects to
ensure when you click &lt;code&gt;run&lt;/code&gt; in your editor, you can run your code. Hopefully it
also explains a bit why when something may not be working &lt;em&gt;exactly&lt;/em&gt; how you want
it to, there is a lot that can go wrong in various places. There's a lot of
extra things related to DAP that we haven't hit on in here that could each be a
post of their own.&lt;/p&gt;
&lt;p&gt;I hope this was insightful. Thanks for reading along.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/the-debug-adapter-protocol-and-scala</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/the-debug-adapter-protocol-and-scala</guid><pubDate>Sun, 13 Mar 2022 00:00:00 +0100</pubDate></item><item><title>Music of the Month - Feb 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - Feb 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - Feb 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---feb-2022&quot; id=&quot;music-of-the-month---feb-2022&quot;&gt;Music of the Month - Feb 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;February was a weird month for me. I tested positive for Covid, and it just
knocked me out for a couple of weeks. I was amazed at how long I stayed sick. I
wasn't able to do a whole lot, and staring at my computer wasn't really an
option, so I spent a lot of time listening to new music, and listening intently
without doing anything else. I always find this the best way to truly experience
an album: sit down, put on some headphones, and listen through the entire album.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My album of the month for February is &lt;em&gt;Love&lt;/em&gt; by Amen Dunes. I've only briefly
listened to Amen Dunes in the past, never his full albums. The only song of his
that I would have recognized was &lt;em&gt;Song of the Siren&lt;/em&gt;, but I randomly came across
the song &lt;em&gt;Love&lt;/em&gt; which lead me to the album which I immediately listened to in
its entirety. This album brings you on a journey through joy, grief, heartbreak,
and solace. It's a great album to listen to when you don't feel well and just
want to immerse yourself in some low frills authentic good music. Fun fact, some
of the members from Godspeed You! Black Emperor, one of my favorites, played on
this album. If you know GY!BE you'll understand why this is notable seeing how
radically different this albums is than anything they've put out. This is a
fantastic album.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; max-width: 350px; height: 786px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=268674417/size=large/bgcol=333333/linkcol=ffffff/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://amendunes.bandcamp.com/album/love&quot;&gt;Love by Amen Dunes&lt;/a&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I listened to a lot of jazz this month. If it wasn't for the album above, the
album this next song came off of could have also easily been the album of the
month for me. The song of the month for me this month was &lt;em&gt;Glad to be Unhappy&lt;/em&gt;
by Paul Desmond. Such a smooth chill song. If you're unfamiliar with Paul
Desmond this entire album, also named &lt;em&gt;Glad to be Unhappy&lt;/em&gt;, is a great
introduction.&lt;/p&gt;
&lt;iframe max-width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/l9ckU_FLQR4&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-feb-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-feb-2022</guid><pubDate>Thu, 3 Mar 2022 00:00:00 +0100</pubDate></item><item><title>Music of the Month - Jan 2022</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Music of the Month - Jan 2022&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;Music I enjoyed this past month&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Music of the Month - Jan 2022&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#music-of-the-month---jan-2022&quot; id=&quot;music-of-the-month---jan-2022&quot;&gt;Music of the Month - Jan 2022&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This is the first post of a monthly series I'd like to do where I share the
music I've enjoyed over the past month. My music tastes are all over the place
and I've found that it's often a good picture of my mood that month, what I've
been going through, or just what I've been enjoying. These won't necessarily be
new albums or songs, but whatever I find myself listening to a lot during the
month. At the end of the month I'll pick an album I felt left a mark and a song
that has been my &amp;quot;favorite&amp;quot;. Give it a listen, and let's talk music.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#album-of-the-month&quot; id=&quot;album-of-the-month&quot;&gt;Album of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My album of the month for January is &lt;em&gt;It's Nice Outside&lt;/em&gt; by Anti-Lilly &amp;amp;
Phoniks. This album came out of nowhere for me. I wasn't aware of them at all
before this month and I don't even remember how I got turned onto it. I heard
&lt;em&gt;Every Season&lt;/em&gt; first, and then I was hooked. The entire album just rattles
through making it through tough times and coming out of the other side. The
lyrics are raw, but balanced with Anti-Lilly's positive outlook. This is such a
fantastic album.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; max-width: 350px; height: 786px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=1321219507/size=large/bgcol=333333/linkcol=ffffff/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://antilillyandphoniks.bandcamp.com/album/its-nice-outside&quot;&gt;It&amp;#39;s Nice Outside by Anti-Lilly &amp;amp; Phoniks&lt;/a&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a href=&quot;#song-of-the-month&quot; id=&quot;song-of-the-month&quot;&gt;Song of the Month&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A song that I found made me pause almost every time it came on this month was
&lt;em&gt;A-001&lt;/em&gt; by Corntuth off their album &lt;em&gt;Music to Work To&lt;/em&gt;. January is also a pretty
reflective month for me as I look back at the previous year and plan for the
current. This song definitely facilitated some of that for me this month.&lt;/p&gt;
&lt;iframe style=&quot;border: 0; width: 350px; height: 470px;&quot; src=&quot;https://bandcamp.com/EmbeddedPlayer/album=2879227229/size=large/bgcol=333333/linkcol=ffffff/tracklist=false/track=1942059846/transparent=true/&quot; seamless&gt;&lt;a href=&quot;https://corntuth.bandcamp.com/album/music-to-work-to&quot;&gt;Music To Work To by Corntuth&lt;/a&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;See you next month&lt;/em&gt;&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/music-of-the-month-jan-2022</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/music-of-the-month-jan-2022</guid><pubDate>Tue, 1 Feb 2022 00:00:00 +0100</pubDate></item><item><title>My Dos and Donts in sbt Build Definitions</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;These are rules I like to follow when writing my build definitions using sbt.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;My Dos and Donts in sbt Build Definitions&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;These are rules I like to follow when writing my build definitions using sbt.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;My Dos and Donts in sbt Build Definitions&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#my-dos-and-donts-in-sbt-build-definitions&quot; id=&quot;my-dos-and-donts-in-sbt-build-definitions&quot;&gt;My Dos and Don'ts in sbt Build Definitions&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Over the past couple of years I've been able to dig around a lot of sbt build
files. Whether &lt;a href=&quot;https://www.scala-sbt.org/index.html&quot;&gt;sbt&lt;/a&gt; is your Scala build
tool of choice or not, you'll likely need to deal with it at some time or
another. sbt baffled me when I first got started. The simple things were
actually pretty simple, but anything else sort of forces you to get a
better-than-surface-level understanding of sbt. Depending on that level of
understanding, the team writing that build definition, or even the type of
project your writing it for provides for a nice smörgåsbord of examples out
there. All sorts of fun and crazy stuff. Because of this, I thought I'd make a
list of Dos and Don'ts I typically try to follow when writing build definitions
in sbt. These opinions were formed with a mixture of open-source and
professional work, annoyances with incredibly complex builds, and probably some
straight-up misunderstandings on my part since sbt still baffles me at times.&lt;/p&gt;
&lt;p&gt;This list isn't exhaustive, it's highly opinionated, and is tainted by specific
experiences and projects I've been a part of. More than anything, the goal of
this is to hopefully spark a conversation in your team about build definitions,
how you write them, and best practices to follow. Sometimes the best practice is
simply having your team all on the same page and doing things the same way.&lt;/p&gt;
&lt;p&gt;So without further ado, here are my personal Dos and Don'ts&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#dont-make-separate-build.sbt-files-for-your-different-modules&quot;&gt;Don't make separate &lt;code&gt;build.sbt&lt;/code&gt; files for your different modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#do-stay-away-from-bare-settings&quot;&gt;Do stay away from bare settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#dont-let-your-settings-leak-into-modules-they-dont-need-to-be-in&quot;&gt;Don't let your settings leak into modules they don't need to be in&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#do-regularly-go-over-your-entire-build-definition&quot;&gt;Do regularly go over your entire build definition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#dont-shade-things-unless-you-absolutely-have-to&quot;&gt;Don't shade things unless you absolutely have to&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#do-stick-to-lazy-vals-everywhere&quot;&gt;Do stick to &lt;code&gt;lazy val&lt;/code&gt;s everywhere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#dont-ever-hardcode-constants&quot;&gt;Don't ever hardcode constants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#do-stay-away-from-crazy-meta-meta-build-stuff&quot;&gt;Do stay away from crazy meta meta build stuff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#dont-put-stuff-in-project-unless-it-is-a-collection-of-something-or-is-standalone&quot;&gt;Don't put stuff in &lt;code&gt;project/&lt;/code&gt; unless it is a collection of something or is &amp;quot;standalone&amp;quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#do-have-a-conversation-with-your-team-about-standards-and-consistency&quot;&gt;Do have a conversation with your team about standards and consistency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#dont-make-your-own-way-to-do-something-if-sbt-already-provides-a-way&quot;&gt;Don't make your own way to do something if sbt already provides a way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#do-document-it-when-you-do-weird-shit&quot;&gt;Do document it when you do weird shit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#dont-add-stuff-to-your-build-definition-you-dont-understand&quot;&gt;Don't add stuff to your build definition you don't understand&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#dont-make-separate-buildsbt-files-for-your-different-modules&quot; id=&quot;dont-make-separate-buildsbt-files-for-your-different-modules&quot;&gt;Don't make separate &lt;code&gt;build.sbt&lt;/code&gt; files for your different modules&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like everything, there seems to be trends in build definitions. One trend I saw
for a while (and you may still use it) is to keep a separate build file for
every project in your build. Meaning you'll likely have a structure like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;.
├── a
│  ├── build.sbt
│  └── src
├── b
│  ├── build.sbt
│  └── src
├── build.sbt
├── c
│  ├── build.sbt
│  └── src
├── d
│  ├── build.sbt
│  └── src
└── project
   └── build.properties
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My experience with this type of setup is that you end up jumping between your
root &lt;code&gt;build.sbt&lt;/code&gt; and your project-specific &lt;code&gt;build.sbt&lt;/code&gt; and probably even some
&lt;code&gt;project/*.scala&lt;/code&gt; to figure out where your stuff is defined and where you need
to change it. The projects I've seen use this style of multiple &lt;code&gt;build.sbt&lt;/code&gt;
files often make heavy use of &lt;code&gt;project/*.scala|*.sbt&lt;/code&gt; files making the situation
even worse. In theory I like this idea, but in practice I hate it. I actually
don't see this nearly as often anymore with new projects. Just stick to a single
&lt;code&gt;build.sbt&lt;/code&gt; file in the root of your directory which contains all the settings
for each separate project. It makes things easier to find, and then your entire
build definition is basically contained in one place.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Taken from the &lt;a href=&quot;https://www.scala-sbt.org/1.x/docs/Multi-Project.html&quot;&gt;sbt
docs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We recommend putting all project declarations and settings in the root
build.sbt file in order to keep all build definition under a single file.
However, it’s up to you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a href=&quot;#do-stay-away-from-bare-settings&quot; id=&quot;do-stay-away-from-bare-settings&quot;&gt;Do stay away from bare settings&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My beef with bare settings can be illustrated with a very minimal build
definition. Let's say we start a new project with an extremely minimal setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;scalaVersion := &amp;quot;3.1.0&amp;quot;

lazy val root = (project in file(&amp;quot;.&amp;quot;))
  .settings(
    name := &amp;quot;bare-settings&amp;quot;
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just to make sure we're all on the same page &lt;code&gt;scalaVersion&lt;/code&gt; is the bare setting
here. Now this works fine as long as root is your only project. &lt;em&gt;However&lt;/em&gt; more
than likely you'll eventually want to expand this to have another project. Let's
pretend we'd like to add a &lt;code&gt;domain&lt;/code&gt; module. So we do that like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;scalaVersion := &amp;quot;3.1.0&amp;quot;

lazy val root = (project in file(&amp;quot;.&amp;quot;))
  .settings(
    name := &amp;quot;bare-settings&amp;quot;
  )

lazy val domain = project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you might see the issue with the immediately if you're familiar with sbt
and how it's various scopes work, but keep in mind that most newcomers to Scala
won't, and they will immediately be met with confusion when they discover the
following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sbt:bare-settings&amp;gt; projects
[info] In file:/Users/ckipp/Documents/scala-workspace/bare-settings/
[info]     domain
[info]   * root
sbt:bare-settings&amp;gt; scalaVersion
[info] 3.1.0
sbt:bare-settings&amp;gt; domain/scalaVersion
[info] 2.12.14
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you don't understand why &lt;code&gt;domain&lt;/code&gt; is on 2.12.14 when you set &lt;code&gt;scalaVersion&lt;/code&gt;
to 3.1.0, it's because you're getting bit by an assumption that a bare settings
should be carried over to your subprojects. There is an interesting discussion
&lt;a href=&quot;https://github.com/sbt/sbt/issues/6217&quot;&gt;here&lt;/a&gt; where &lt;a href=&quot;https://twitter.com/eed3si9n&quot;&gt;Eugene
Yokota&lt;/a&gt; proposes to deprecate and eventually
remove bare settings in hope to avoid this confusion. It explains a bit of the
history behind them, ideas to change the default behavior to be less confusing,
and also contains some arguments &lt;em&gt;for&lt;/em&gt; having them. It's worth a read.&lt;/p&gt;
&lt;p&gt;The example up above is minimal, but you can see how once your project becomes
larger and you have numerous bare settings how confusing this could become. The
main argument for them is to help someone new to sbt since just using
&lt;code&gt;scalaVersion&lt;/code&gt; is easier than &lt;code&gt;ThisBuild / scalaVersion&lt;/code&gt;. That's fair, since the
latter also forces knowledge of &lt;code&gt;ThisBuild&lt;/code&gt;. So the question really is, does the
default behavior of bare settings make sense? Again, read the linked thread
above for a much more detailed conversation on this.&lt;/p&gt;
&lt;p&gt;My takeaway and personal experience is to avoid bare settings completely. The
time spent explaining to your team why will be time better spent than a new
teammate banging their head against the wall when they are bit by it later on if
you don't and you use bare settings.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#dont-let-your-settings-leak-into-modules-they-dont-need-to-be-in&quot; id=&quot;dont-let-your-settings-leak-into-modules-they-dont-need-to-be-in&quot;&gt;Don't let your settings leak into modules they don't need to be in&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A common approach you'll see in a lot of builds is making use of common
settings. You'll often see something like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;lazy val api = project
  .settings(commonSettings)
  .dependsOn(domain)

lazy val domain = project
  .settings(commonSettings)

lazy val commonSettings = Seq(
  scalaVersion := &amp;quot;3.1.0&amp;quot;,
  libraryDependencies += &amp;quot;com.lihaoyi&amp;quot; %% &amp;quot;cask&amp;quot; % &amp;quot;0.8.0&amp;quot;,
  scalacOptions += &amp;quot;-explain&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the time having some sort of common settings is a good thing, but many
times you'll end up with settings that are leaking into other projects that you
don't actually want. In the above example there is no reason cask needs to be a
dependency of domain, but it is. You might think this would never actually
happen since it's so obvious you don't need a HTTP framework as a dependency in
your domain module, but I've seen this happen time and time again. I've even
guilty of it at times. This can lead to bloating your dependencies for a project
causing you to rely on things you don't need, having invalid &lt;code&gt;scalacOptions&lt;/code&gt; for
projects, or just build settings being applied in places you don't want them,
and maybe don't even realize they are being applied. Be extra careful with
common settings and ensure that what you have in there truly are meant to be
share every place that is using them. Again, this seems obvious in such a
minimal example, but add hundreds of lines of sbt code to this and you'll
quickly see the problem.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#do-regularly-go-over-your-entire-build-definition&quot; id=&quot;do-regularly-go-over-your-entire-build-definition&quot;&gt;Do regularly go over your entire build definition&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This actually relates to the above point about being careful with your common
settings. I've been amazed at the amount of times I've been looking through a
build file and thought &amp;quot;What is this doing in here, this was for an issue fixed
&lt;em&gt;long&lt;/em&gt; ago&amp;quot;. It seems that we never refactor build definitions. We put them
together, add some hacks, and then leave them until we need to add another hack.
We forget that our build definition is also code, ripe for refactoring, and
arguably will always have stuff left over that doesn't need to be in there. This
also relates to other points I'll make about ensuring that when you add
something to a build definition to avoid a bug, work around an issue, etc, you
should also add a note as to what it's doing and why you're doing it.&lt;/p&gt;
&lt;p&gt;When you change something in your build, take a bit of time, walk through it,
make sure things are up to date, make sure exclusions that are there still need
to be there, dependency overrides still need to be there, and other random
settings you copied off Stack Overflow to overcome some issue still need to be
there. This helps keep those build definitions nice and tidy and avoids the &amp;quot;Is
this still relevant&amp;quot; question I so often ask when looking at build files.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#dont-shade-things-unless-you-absolutely-have-to&quot; id=&quot;dont-shade-things-unless-you-absolutely-have-to&quot;&gt;Don't shade things unless you absolutely have to&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hesitated to put this in here since it's maybe more specific to just
dependency management, but I thought it helped reinforce the idea of keeping
things simple. Shading at times is necessary and even wise to ensure you don't
force a transitive dependency on a downstream user, but in those situations you
are adding a layer of complexity to your build definition that will cause others
to have to spend the time understanding why it was done. An argument could be
made that if this is clearly documented &lt;em&gt;why&lt;/em&gt; then it's no issue, but I've often
found that when shading is used you get a single line comment at most. Sometimes
this is enough, but in other cases I've found hundreds of lines of code related
to shading with no explanation. Understand that this can be almost impossible to
decipher without the full backstory of why it was done.&lt;/p&gt;
&lt;p&gt;My default rule is to avoid shading unless you absolutely have to do it and the
benefit outweighs any complexity and confusion you may be adding to your build
definition.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#do-stick-to-lazy-vals-everywhere&quot; id=&quot;do-stick-to-lazy-vals-everywhere&quot;&gt;Do stick to &lt;code&gt;lazy val&lt;/code&gt;s everywhere&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This one may seem obvious to some, but it's also a commonly asked question. The
most simple answer is that to avoid having to define everything before you use
it, use lazy vals. The below example is something you'll see fairly often. Now
if you start removing &lt;code&gt;lazy&lt;/code&gt; from various places you will break stuff since if
you have a project that isn't lazy and tries to access another project before
it's defined or commons settings before it's defined you will run into issues.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;ThisBuild / scalaVersion := &amp;quot;3.1.0&amp;quot;

lazy val V = new {
  val cask = &amp;quot;0.8.0&amp;quot;
}

lazy val api = project
  .settings(commonSettings)
  .settings(
    libraryDependencies += &amp;quot;com.lihaoyi&amp;quot; %% &amp;quot;cask&amp;quot; % V.cask
  )
  .dependsOn(domain)

lazy val domain = project
  .settings(commonSettings)

lazy val commonSettings = Seq(
  scalacOptions += &amp;quot;-explain&amp;quot;
)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To keep it simple for myself, every time I use a val at the top level in my sbt
file, I make it lazy. One related thing I've seen in build files that &lt;em&gt;isn't&lt;/em&gt;
needed is nested lazy modifies like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;lazy val V = new {
   lazy val cask = &amp;quot;0.8.0&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;#dont-ever-hardcode-constants&quot; id=&quot;dont-ever-hardcode-constants&quot;&gt;Don't ever hardcode constants&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is actually a best practice taken right &lt;a href=&quot;https://www.scala-sbt.org/1.x/docs/Best-Practices.html#don%e2%80%99t+hard+code&quot;&gt;from the
docs&lt;/a&gt;.
Since it's in the docs I'll directly quote from there and use the same example.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Don’t hard code constants, like the output directory target/. This is
especially important for plugins. A user might change the target setting to
point to build/, for example, and the plugin needs to respect that. Instead,
use the setting, like:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;myDirectory := target.value / &amp;quot;sub-directory&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;#do-stay-away-from-crazy-meta-meta-build-stuff&quot; id=&quot;do-stay-away-from-crazy-meta-meta-build-stuff&quot;&gt;Do stay away from crazy meta meta build stuff&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The amount of projects that actually need this is incredibly small, so maybe
you'll never see this. However, I've come across projects with
&lt;code&gt;project/project/project/build.sbt&lt;/code&gt; which is ludicrous. The mental gymnastics
that a user needs to go through to understand what's happening here can give
them nightmares. Are there actual use cases for including logic in your sbt
meta-build? Absolutely, but I question if you ever need to go another layer
deeper. Most users won't understand what's happening or how you even interact or
see settings defined there.&lt;/p&gt;
&lt;p&gt;If you're a Metals user you may be thinking &amp;quot;Wait a second, Metals creates a
&lt;code&gt;project/project/metals.sbt&lt;/code&gt;... you hypocrite!&amp;quot;. If you're using Metals and
Bloop as your build server, Metals needs to export your build to Bloop. The only
way to do that is to go one layer up and add the &lt;code&gt;sbt-bloop&lt;/code&gt; plugin. So if you
have stuff in your &lt;code&gt;project/*.sbt&lt;/code&gt; we need to add a &lt;code&gt;metals.sbt&lt;/code&gt; in your
&lt;code&gt;project/project/&lt;/code&gt;. If you have something in your  &lt;code&gt;project/project/*.sbt&lt;/code&gt;, we
would also need to add the plugin in your &lt;code&gt;project/project/project/&lt;/code&gt; dir. You
get the idea. This goes to show that in reality there &lt;em&gt;are&lt;/em&gt; some use cases for
this, but they are very few. If you can, avoid it.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#dont-put-stuff-in-project-unless-it-is-a-collection-of-something-or-is-standalone&quot; id=&quot;dont-put-stuff-in-project-unless-it-is-a-collection-of-something-or-is-standalone&quot;&gt;Don't put stuff in &lt;code&gt;project/&lt;/code&gt; unless it is a collection of something or is &amp;quot;standalone&amp;quot;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another rule I like to follow is to not include things in &lt;code&gt;project/&lt;/code&gt; unless it's
something that is &amp;quot;standalone&amp;quot; or a collection. A couple examples of these.&lt;/p&gt;
&lt;p&gt;At work we use a private nexus, and when you publish sbt plugins maven style, it
doesn't create a &lt;code&gt;maven-metadata.xml&lt;/code&gt; file. NOTE: That it &lt;em&gt;does&lt;/em&gt; create one for
external plugins that you are mirroring, but not ones you publish yourself. This
ends up being problematic when you run Scala Steward, since under the hood
coursier needs a &lt;code&gt;maven-metadata.xml&lt;/code&gt; or an index that it can scan, but in this
scenario has neither. You can see the context for what I explained
&lt;a href=&quot;https://github.com/coursier/coursier/issues/1874&quot;&gt;here&lt;/a&gt;. However, the way we
got around this issue was to create a custom release step for
&lt;a href=&quot;https://github.com/sbt/sbt-release&quot;&gt;sbt-release&lt;/a&gt; which created and/or updated
the metadata file and was simply called during the release process. We stuck in
in &lt;code&gt;/project/CustomReleaseStep.scala&lt;/code&gt;. It's only referenced once from the
&lt;code&gt;build.sbt&lt;/code&gt; file, and apart from that never touched, never inherited from, and
has no impact on the rest of the build definition. This is a great example of
something that is &amp;quot;standalone&amp;quot; and belongs there.&lt;/p&gt;
&lt;p&gt;The other time I like to put stuff in the &lt;code&gt;project/&lt;/code&gt; is when it's a collection
of sorts. This can be a collection of dependencies that you're using in your
build, a collection of maintainers, or something similar. These are also great
things to stick in &lt;code&gt;project/&lt;/code&gt; to not take up a ton of space in your &lt;code&gt;build.sbt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Apart from those two things, I like to stick everything else in my &lt;code&gt;build.sbt&lt;/code&gt;.
I dislike when projects have a bunch of &lt;code&gt;project/*.scala&lt;/code&gt; files with complex
logic that conditionally set keys making it very difficult to understand where
something is being set.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#do-have-a-conversation-with-your-team-about-standards-and-consistency&quot; id=&quot;do-have-a-conversation-with-your-team-about-standards-and-consistency&quot;&gt;Do have a conversation with your team about standards and consistency&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This might be the most important one on the list. Most of the time you don't
work in isolation meaning that others will be touching your build definition.
The benefit of having everyone on your team on the same page far outweighs all
the other best practices since everyone hopefully understands the patterns
you're following, the reasons you're doing them, and is more quickly able to
edit and iterate on them. If you've never had a discussion with your team about
your build definition, what standards you want to follow, and document those,
you should. If you don't you'll end up with build definitions that are wildly
different from one another, build definitions that have mixed ways of doing
things, and ultimately creating technical debt the moment you add something to
your build.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#dont-make-your-own-way-to-do-something-if-sbt-already-provides-a-way&quot; id=&quot;dont-make-your-own-way-to-do-something-if-sbt-already-provides-a-way&quot;&gt;Don't make your own way to do something if sbt already provides a way&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It might be tempting to try and come up with your own solutions when you don't
like the &amp;quot;sbt&amp;quot; way of doing things. For the sake of an illustration let's
pretend we hate the way it's currently done to have cross-versioned sources. You
have some Scala 2 code and some Scala 3 code you want side by side. You don't
like the default &lt;code&gt;src/main/scala-&amp;lt;scala binary version&amp;gt;/&lt;/code&gt; being the way it
includes Scala-version specific sources so you decide to come up with your own
&amp;quot;better&amp;quot; solution.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;Compile / sources := {
  (Compile / sources).value.filter(_.getName().endsWith(&amp;quot;2&amp;quot;))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was easy, now it can quickly pick up all my Scala 2 sources, I just need to
make sure they end with Scala 2, and then my Scala 3 ones can end with 3. You
now just broke away from the expected norm for anyone familiar with sbt. What
they expect to work won't, and you force the user to dig into what's going on to
add an extra layer of complexity they must understand just to do something that
sbt can already do. This can be incredibly frustrating for contributors to a
project that just want to quickly contribute something, but are first forced to
understand the intricacies of your Frankenstein build definition.&lt;/p&gt;
&lt;p&gt;Are there exceptions to this rule? Probably.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#do-document-it-when-you-do-weird-shit&quot; id=&quot;do-document-it-when-you-do-weird-shit&quot;&gt;Do document it when you do weird shit&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Picture this, you've been struggling all week on getting something to work. You
feel like you've read the entire sbt docs site, all the sbt code you could
actually understand, and then you finally come up with a solution to your
bespoke problem. It's wild, it's crazy, and it makes perfect sense to you in
that moment. For the sake of anyone coming across that code in the future, if
it's absolutely necessary to keep it, document it. Document what you tried, why
it didn't work, why you chose to do whatever you did, and over explain it. I've
seen projects do wild stuff, and that undocumented wild stuff was enough to make
them not even attempt to work on the project, even if it desperately needs it.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#dont-add-stuff-to-your-build-definition-you-dont-understand&quot; id=&quot;dont-add-stuff-to-your-build-definition-you-dont-understand&quot;&gt;Don't add stuff to your build definition you don't understand&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At times it's tempting to copy some code into your build definition, confirm it
works, and then call it a day. I encourage you not to do this. Mainly because
having parts of your build definition that you don't understand is a liability
if you need to change it but don't understand what you're changing. I've
personally found the Scala tooling community incredibly helpful and open to
questions of all levels. I've asked a lot of sbt questions in the
&lt;a href=&quot;https://gitter.im/sbt/sbt&quot;&gt;sbt/sbt&lt;/a&gt; gitter chat, and they almost always got
answered.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After reading this you may totally disagree with some of these rules, and that's
absolutely fine. Think about them, discuss them with your team, and be
consistent in the way you apply them while also understanding there are always
exceptions to the rules. Thanks for reading.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/my-dos-and-donts-in-sbt-build-definitions</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/my-dos-and-donts-in-sbt-build-definitions</guid><pubDate>Sun, 19 Dec 2021 00:00:00 +0100</pubDate></item><item><title>An intro to the Scala presentation compiler</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;An introduction to what the Scala presentation compiler is and an example of how to use it.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;An intro to the Scala presentation compiler&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;An introduction to what the Scala presentation compiler is and an example of how to use it.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;An intro to the Scala presentation compiler&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#an-intro-to-the-scala-presentation-compiler&quot; id=&quot;an-intro-to-the-scala-presentation-compiler&quot;&gt;An intro to the Scala Presentation Compiler&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&amp;quot;Completions are powered by the presentation compiler&amp;quot;. I remember the first
time I read this phrase. As someone that was fairly new to Scala, new to working
in tooling, and had never touched the internals of a compiler before, this
phrase made absolutely no sense to me. I had no idea what the presentation
compiler was, where it was located, and definitely no idea of how to use it. To
be honest, I was also a bit afraid to even ask those questions. Often when we
see phrases like &amp;quot;it's simple&amp;quot; or &amp;quot;it's easy&amp;quot;, it really just means &amp;quot;I'm
familiar with this&amp;quot;.&lt;/p&gt;
&lt;p&gt;A lot of topics in tooling seem to be less covered than those of hot libraries
or general programming paradigms, which totally makes sense. There is a much
smaller group of people working in this area. However, it does then make it a
bit harder to get started. Thankfully, the Scala tooling ecosystem is packed
full of friendly helpful people that have always been more than helpful whenever
I've gotten stuck or had no clue where to start with something. Plus a lot of
the Scala documentation is incredibly useful and underappreciated. Like this
page on
&lt;a href=&quot;https://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html#trees&quot;&gt;Trees&lt;/a&gt;,
which will be relevant for what I show down below.&lt;/p&gt;
&lt;p&gt;The goal for this post is so that hopefully when someone in the future hears a
phrase about the presentation compiler and has no idea what it is, this can
serve as an introduction to what it is, where to find it, and as an example of
how to use it. When I first started searching around for these answers I came
across a few useful sources that I'll reference since they helped me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://scala-ide.org/docs/dev/architecture/presentation-compiler.html&quot;&gt;The Scala Presentation
Compiler&lt;/a&gt;,
which gives a brief overview of how the Eclipse Scala IDE used the Scala
presentation compiler.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ensime.blogspot.com/2010/08/building-ide-with-scala-presentation.html&quot;&gt;Building an IDE with the Scala Presentation Compiler: A Field
Report&lt;/a&gt;,
which gives a great overview of how Ensime used the Scala presentation
compiler.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/16538791/scala-presentation-compiler-minimal-example&quot;&gt;This stack overflow
post&lt;/a&gt;,
which gives a great minimal standalone example of how to use the
presentation compiler.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=R77XYOtuomE&quot;&gt;Integrating IDEs with Dotty, The Experimental Scala Compiler by Guillaume
Martres&lt;/a&gt;, where Guillaume does
a great job at explaining how IDEs can interact with Dotty.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#what-is-the-presentation-compiler&quot; id=&quot;what-is-the-presentation-compiler&quot;&gt;What is the presentation compiler&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The above mentioned post by the Scala IDE teams defines the presentation
compiler as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Scala IDE for Eclipse uses the Scala Presentation Compiler, a faster
asynchronous version of the Scala Compiler. The presentation compiler only
runs the phases up until and including the typer phase, that is, the first 4
of the 27 scala compilation phases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a really great minimal explanation of what the Scala Presentation
Compiler is. It's specifically made for usage from IDE-like tools that need a
compiler that is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Asynchronous&lt;/li&gt;
&lt;li&gt;Interruptible at every point&lt;/li&gt;
&lt;li&gt;Can do targeted typechecking&lt;/li&gt;
&lt;li&gt;Stops after a specific point (type-checking) and provides a partial result&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Martin Odersky actually explains this a bit &lt;em&gt;way back in 2011&lt;/em&gt; when giving a
&lt;a href=&quot;https://www.youtube.com/watch?v=qqQNqIy5LdM&amp;amp;t=1320s&quot;&gt;talk&lt;/a&gt; about what's coming
in Scala 2.10. You may also be wondering where this thing is in the Scala code
base.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For Scala 3 you can find it located &lt;a href=&quot;https://github.com/lampepfl/dotty/tree/master/compiler/src/dotty/tools/dotc/interactive&quot;&gt;here in the interactive
package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For Scala 2 it's &lt;a href=&quot;https://github.com/scala/scala/tree/2.13.x/src/interactive/scala/tools/nsc/interactive&quot;&gt;here in the interactive
package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also keep in mind that the compiler internals are completely different between 2
and 3, meaning that the presentation compiler API is completely different. So
for tooling that interfaces with the presentation compiler, they'll need to
account for this. For example that's why in the Metals
&lt;a href=&quot;https://github.com/scalameta/metals/tree/main/mtags/src/main/scala-3/scala/meta/internal/pc&quot;&gt;mtags&lt;/a&gt;
module, which is a module to abstract over compiler access, you'll see that
Scala 3 has a completely different implementation.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#what-uses-the-presentation-compiler&quot; id=&quot;what-uses-the-presentation-compiler&quot;&gt;What uses the presentation compiler&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you haven't already gotten this from looking at where these articles are
coming from, you'll notice that &lt;em&gt;many&lt;/em&gt; of the IDE-like tools or tools that
provide IDE-like features in the Scala Ecosystem use or have used the Scala
Presentation Compiler.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://scala-ide.org/&quot;&gt;Scala IDE for Eclipse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ensime.github.io/&quot;&gt;The Ensime project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scalameta.org/metals/&quot;&gt;Metals, the Scala Language Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ammonite.io/&quot;&gt;Ammonite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scalameta.org/mdoc/&quot;&gt;mdoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.scala-lang.org/overviews/repl/overview.html&quot;&gt;The Scala REPL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You may have noticed that &lt;a href=&quot;jetbrains.com/idea/&quot;&gt;IntelliJ&lt;/a&gt; is missing from that
list. That's because IntelliJ actually uses its own implementation of the Scala
Presentation Compiler. That's why it's possible to see subtle differences with
diagnostics showing in IntelliJ that don't show when compiling with the actual
Scala compiler.&lt;/p&gt;
&lt;p&gt;Many of these projects use the presentation compiler to accomplish very similar
goals, and that is to provide the common features that you'd expect from an
editor. One of those common features is completions. For example you can see a
lot of the functionality for this in the aptly named
&lt;a href=&quot;https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/interactive/Completion.scala&quot;&gt;Completion.scala&lt;/a&gt;
file in the Scala 3 codebase. The presentation compiler can do the majority of
the things you'd expect to see in an editor like, get the hover information on a
symbol, find the references of a symbol, or go to the definition site of a
symbol. A really nice example of all this can be found in the
&lt;a href=&quot;https://github.com/lampepfl/dotty/blob/master/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala&quot;&gt;&lt;code&gt;DottyLanguageServer.scala&lt;/code&gt;&lt;/a&gt;,
which was an embedded
&lt;a href=&quot;https://microsoft.github.io/language-server-protocol/&quot;&gt;LSP&lt;/a&gt; server for Scala 3
as the team was developing Dotty. The presentation compiler can be used for a
whole variety of things, and I'll give a very specific example of a minimal
feature that was just added to Metals in
&lt;a href=&quot;https://scalameta.org/metals/blog/2021/07/14/tungsten#add-support-for-textdocumentselectionrange&quot;&gt;0.10.5&lt;/a&gt;
down below.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#a-minimal-example-of-using-the-presentation-compiler&quot; id=&quot;a-minimal-example-of-using-the-presentation-compiler&quot;&gt;A minimal example of using the presentation compiler&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One thing I've found in the past is that there isn't a lot of standalone
examples out there of using the presentation compiler. It's not that surprising,
since why would there be, but recently while working on a new feature for
Metals, I thought it was a perfect small example of how the presentation
compiler could be used to implement an editor feature that is maybe a little
less common than the text-book examples of completions, definitions, or
references. If you'd like to follow along or play with the code I'm about to
show, there are examples for both Scala 2 and 3 located
&lt;a href=&quot;https://github.com/ckipp01/presentation-compiler-examples&quot;&gt;here&lt;/a&gt;. In the
examples down below we'll focus on Scala 3.&lt;/p&gt;
&lt;p&gt;The feature I wanted to implement in Metals was the &lt;a href=&quot;https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_selectionRange&quot;&gt;LSP
&lt;code&gt;textDocument/selectionRange&lt;/code&gt;&lt;/a&gt;.
This feature allows you to more intelligently expand a selection. This is a
useful feature when you maybe want to quickly select everything inside of a
parameter, select an entire method, or really just anything one level above
where you are. Here is a quick gif showing what I mean:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/1TSIvfI.gif&quot; alt=&quot;selection range example&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you were to look at the LSP communication for this request, you'd see the
response from the server would look something like this for the above example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[Trace - 00:07:29 AM] Sending response 'textDocument/selectionRange - (70)'. Processing request took 8ms
Result: [
  {
    &amp;quot;range&amp;quot;: {
      &amp;quot;start&amp;quot;: {
        &amp;quot;line&amp;quot;: 2,
        &amp;quot;character&amp;quot;: 14
      },
      &amp;quot;end&amp;quot;: {
        &amp;quot;line&amp;quot;: 2,
        &amp;quot;character&amp;quot;: 28
      }
    },
    &amp;quot;parent&amp;quot;: {
      &amp;quot;range&amp;quot;: {
        &amp;quot;start&amp;quot;: {
          &amp;quot;line&amp;quot;: 2,
          &amp;quot;character&amp;quot;: 6
        },
        &amp;quot;end&amp;quot;: {
          &amp;quot;line&amp;quot;: 2,
          &amp;quot;character&amp;quot;: 29
        }
      },
      &amp;quot;parent&amp;quot;: {
        &amp;quot;range&amp;quot;: {
          &amp;quot;start&amp;quot;: {
            &amp;quot;line&amp;quot;: 1,
            &amp;quot;character&amp;quot;: 2
          },
          &amp;quot;end&amp;quot;: {
            &amp;quot;line&amp;quot;: 2,
            &amp;quot;character&amp;quot;: 29
          }
        },
        &amp;quot;parent&amp;quot;: {
          &amp;quot;range&amp;quot;: {
            &amp;quot;start&amp;quot;: {
              &amp;quot;line&amp;quot;: 0,
              &amp;quot;character&amp;quot;: 0
            },
            &amp;quot;end&amp;quot;: {
              &amp;quot;line&amp;quot;: 2,
              &amp;quot;character&amp;quot;: 29
            }
          }
        }
      }
    }
  }
]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's see how we can come up with these ranges using the presentation compiler.&lt;/p&gt;
&lt;p&gt;We'll start with the very basic piece of code you saw in the gif. We can either
read this in from a file, or just store it as a string. For now just storing it
in a string is easiest, so we'll do that.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;val ourScalaCode = &amp;quot;&amp;quot;&amp;quot;|object Main:
                      |  @main def hello: Unit =
                      |    println(&amp;quot;Hello world!&amp;quot;)
                      |&amp;quot;&amp;quot;&amp;quot;.stripMargin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'll use &lt;a href=&quot;https://get-coursier.io/&quot;&gt;Coursier&lt;/a&gt; to fetch the actual scala3-library
to pass in when I create an instance of the &lt;code&gt;InteracativeDriver&lt;/code&gt;. If you don't
do this dotc (dotty compiler) will yell at you to ensure that the compiler core
libraries are on the classpath. So we fetch them, and then we create the
&lt;code&gt;InteracativeDriver&lt;/code&gt; aka the Scala 3 presentation compiler like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;
val fetch = Fetch.create()

fetch.addDependencies(
  Dependency.of(&amp;quot;org.scala-lang&amp;quot;, &amp;quot;scala3-library_3&amp;quot;, &amp;quot;3.0.0&amp;quot;)
)

val extraLibraries = fetch
  .fetch()
  .asScala
  .map(_.toPath())
  .toSeq

val driver = new InteractiveDriver(
  List(
    &amp;quot;-color:never&amp;quot;,
    &amp;quot;-classpath&amp;quot;,
    extraLibraries.mkString(File.pathSeparator)
  )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep in mind that this is meant to be a very minimal example with everything in
one place. Everything that we're doing is living in the same file and is only
around 80 lines of code. So if this was a real world project we'd care a little
more about where and how we are managing this driver, how we are setting up the
context etc. For now, we're just creating it for a single run, so we'll
literally keep it as simple as possible.&lt;/p&gt;
&lt;p&gt;Next we'll take &lt;code&gt;ourScalaCode&lt;/code&gt; from up above and create a virtual source file
out of it. We'll then take it and then run it. We can also verify here that
everything is valid with our code. For example we can assert that the run hasn't
returned any diagnostics.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;val filename = &amp;quot;Example.scala&amp;quot;
val uri = java.net.URI.create(s&amp;quot;file:///$filename&amp;quot;)

val sourceFile = SourceFile.virtual(filename, ourScalaCode)

val diagnostics = driver.run(
  uri,
  sourceFile
)

assert(diagnostics.isEmpty)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, we already have everything we need to dive into the compiler tree
for the code that has just ran. We an access the entire untyped tree by doing
the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;val tree: Option[Tree[Untyped]] =
  driver.currentCtx.run.units.headOption.map(_.untpdTree)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, remember we are trying to get the selection ranges starting at a given
point in this code. So we'll pretend that the cursor is in the current position
(the char surrounded by &amp;lt;&amp;lt;&amp;gt;&amp;gt;) when we are going to trigger the expand selection
of the &lt;code&gt;textDocument/selectionRange&lt;/code&gt; feature.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;object Main:
  @main def hello: Unit =
    println(&amp;quot;H&amp;lt;&amp;lt;e&amp;gt;&amp;gt;llo world!&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The offset for this position should be 55. So we'll create this position to use
in a bit. For some of the future calls we are about to make we'll also need a
context provided, so you'll also see that given. Often in the examples you'll
see this done a bit different depending on how the context is created. Lots of
times you'll start with your own fresh context and build it up to be what you
want. This can be doing things like setting the classpath as we did in our
example or setting the output to &lt;code&gt;new VirtualDirectory(&amp;quot;(memory)&amp;quot;)&lt;/code&gt; so any
generated classfiles don't actually get physically stored anywhere. There are a
whole bunch of settings, but again, we're keeping it as simple as possible.
I'll share this code snippet and then we'll dissect exactly what's going on.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;val pos: SourcePosition = new SourcePosition(sourceFile, Spans.Span(55))

given ctx: Context = driver.currentCtx

val trees: List[dotty.tools.dotc.ast.tpd.Tree] =
  Interactive.pathTo(driver.openedTrees(uri), pos)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Firstly, the &lt;code&gt;driver.openedTrees(uri)&lt;/code&gt; does a lookup and returns the full top
level trees that were discovered in the file during the run. The &lt;code&gt;pathTo&lt;/code&gt; method
is an extremely useful method that returns the reverse path to the node in the
tree that has the closest enclosing position on the given position. This list of
trees is exactly what we are looking for to create our selection ranges. Note
that this actual returns a typed tree. We don't actually need a typed tree for
this, and if you look at the Scala 2 example, we do this same thing, but with an
untyped tree. In Metals currently it's done this way mainly due to how easy it
is, but it will probably change in the future work on an untyped tree.&lt;/p&gt;
&lt;p&gt;Now that we have the trees we need, we'll map over them to collect the start and
end positions of the trees, and then only take the unique ones. There are
situations where multiple nodes may have the same range, but we discard any
duplicates.&lt;/p&gt;
&lt;p&gt;If you're curious about the duplicates, here is a minimal example. Given a small
for comprehension like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt; val total = for {
   a &amp;lt;- Some(1)
 } yield a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The AST for this would have multiple nodes that have the exact same range. Here
are two different ones that have the exact same range.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;// The range for this one
Apply(Ident(Some), List(Literal(Constant(1))))
// Is the exact same range as this one
Select(Apply(Ident(Some), List(Literal(Constant(1)))), map)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the above looks foreign to you, the &lt;a href=&quot;https://docs.scala-lang.org/overviews/reflection/overview.html&quot;&gt;Reflection Overview
Page&lt;/a&gt; in the
docs is a great place to look.&lt;/p&gt;
&lt;p&gt;So, once we have these positions, we'll zip them up with their index and
print out the code snippets that correspond with the &amp;quot;ranges&amp;quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;val ranges = trees.map { tree =&amp;gt;
  (tree.sourcePos.start, tree.sourcePos.end)
}.distinct

ranges.zipWithIndex
  .foreach { case ((start, end), index) =&amp;gt;
    val selection = ourScalaCode.slice(start, end)
    pprint
      .copy(colorLiteral = fansi.Color.Blue)
      .pprintln(s&amp;quot;Selection Range: ${index + 1}&amp;quot;)
    pprint.pprintln(selection)
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, we should have the selections that we've been working at getting!
If you watch the gif up above again you should see selection ranges correspond
starting at the smallest enclosing node going all the way up the tree until you
have the entire source file covered.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;&amp;quot;Selection Range: 1&amp;quot;
&amp;quot;\&amp;quot;Hello world!\&amp;quot;&amp;quot;

&amp;quot;Selection Range: 2&amp;quot;
&amp;quot;println(\&amp;quot;Hello world!\&amp;quot;)&amp;quot;

&amp;quot;Selection Range: 3&amp;quot;
&amp;quot;&amp;quot;&amp;quot;@main def hello: Unit =
    println(&amp;quot;Hello world!&amp;quot;)&amp;quot;&amp;quot;&amp;quot;

&amp;quot;Selection Range: 4&amp;quot;
&amp;quot;&amp;quot;&amp;quot;object Main:
  @main def hello: Unit =
    println(&amp;quot;Hello world!&amp;quot;)&amp;quot;&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hopefully this helps give a somewhat clear picture of a real-world use case of
the presentation compiler. If you curious about more complex examples of how
this would be set up in a project, or how presentation compiler features could
be used in real applications, I'll drop a few links down below of places in code
to dig through. As always, if you're interested in getting involved in Scala
tooling but are unsure where to start, don't hesitate to reach out. Thanks for
stopping by.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#some-code-to-dig-through&quot; id=&quot;some-code-to-dig-through&quot;&gt;Some code to dig through&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ckipp01/presentation-compiler-examples/blob/main/scala-3-presentation-compiler-examples/src/SelectionRanges.scala&quot;&gt;&lt;code&gt;SelectionRanges.scala&lt;/code&gt;&lt;/a&gt;
The runnable example code shown above. There is also a Scala 2 example in
the same repo.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scalameta/metals/blob/main/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala&quot;&gt;&lt;code&gt;ScalaPresentationCompiler.scala&lt;/code&gt;&lt;/a&gt;
An overview of everything Metals uses the Scala 3 presentation compiler for.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lampepfl/dotty/blob/master/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala&quot;&gt;&lt;code&gt;DottyLanguageServer.scala&lt;/code&gt;&lt;/a&gt;
The built-in LSP server in the Scala 3 codebase.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/com-lihaoyi/Ammonite/blob/master/amm/compiler/src/main/scala-3/ammonite/compiler/Compiler.scala&quot;&gt;&lt;code&gt;Compiler.scala&lt;/code&gt;&lt;/a&gt;
Shows how Ammonite accesses the Scala 3 compiler and uses various parts of
the presentation compiler. This example is the most different than they
others.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scalameta/mdoc/blob/main/mdoc/src/main/scala-3/mdoc/internal/markdown/MarkdownCompiler.scala&quot;&gt;&lt;code&gt;MarkdownCompiler.scala&lt;/code&gt;&lt;/a&gt;
Shows how mdoc creates and uses the Scala 3 presentation compiler.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scala/scala/blob/2.13.x/src/repl/scala/tools/nsc/interpreter/PresentationCompilation.scala&quot;&gt;&lt;code&gt;PresentationCompilation.scala&lt;/code&gt;&lt;/a&gt;
Showing the usage of the presentation compiler in the Scala 2 REPL.&lt;/li&gt;
&lt;/ul&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/an-intro-to-the-scala-presentation-compiler</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/an-intro-to-the-scala-presentation-compiler</guid><pubDate>Thu, 15 Jul 2021 00:00:00 +0200</pubDate></item><item><title>Scala and Vim Part 1</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;A look at the current state of Vim and Scala&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Scala and Vim Part 1&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;A look at the current state of Vim and Scala&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Scala and Vim Part 1&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#scala-and-vim-part-1&quot; id=&quot;scala-and-vim-part-1&quot;&gt;Scala and Vim Part 1&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I was first introduced to Scala in the summer of 2018. Prior to Scala I worked
in a couple languages with my main up to that point being Javascript. I had
recently started getting into Vim and when I started working with Scala I had
hopes of being able to start with it being my primary editor. At the time, it
seemed like there was only one option for you if you wanted to use Vim with
Scala and have some sort of help with intelligent completions and the like,
&lt;a href=&quot;https://ensime.github.io/&quot;&gt;Ensime&lt;/a&gt;. That paired with the very popular
&lt;a href=&quot;https://github.com/derekwyatt/vim-scala&quot;&gt;vim-scala&lt;/a&gt; plugin by Derek Wyatt
seemed to be all you needed, but for a couple different reasons it was a
nightmare for me to set up. I remember feeling defeated, upset, and dumb. I then
followed my colleagues advice and just started using IntelliJ.&lt;/p&gt;
&lt;p&gt;I used IntelliJ for about a year, and that was around the time in mid 2019 when
there was starting to be a bit more buzz around the new kid on the block:
&lt;a href=&quot;https://scalameta.org/metals/&quot;&gt;Metals&lt;/a&gt;. At the time, the docs had instructions
about using Coursier to bootstrap Metals to be used with
&lt;a href=&quot;https://github.com/natebosch/vim-lsc/&quot;&gt;vim-lsc&lt;/a&gt;. There was a some setup
necessary, but out of the box I was was able to get a good chunk of things
working. While there was definitely room for improvement, this initial small win
for me was enough to start getting more interested in the tooling around Scala
and Vim, and it's when I first started slowly contributing to Metals.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/scalameta.png&quot; alt=&quot;Scalameta logo&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-birth-of-coc-metals&quot; id=&quot;the-birth-of-coc-metals&quot;&gt;The Birth of coc-metals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Pretty soon after starting to contribute it was pretty evident that things like
a simple automated installation process, an easy way to change configuration, or
the ability to easily send commands to the server were necessary for wider
adoption and just a nicety to have, especially if there was any desire to
support some of the &lt;a href=&quot;https://scalameta.org/metals/docs/editors/overview.html#metals-extensions&quot;&gt;LSP
extensions&lt;/a&gt;
that Metals has. This naturally led to me wanting to create a plugin of sorts
to aid in the install and generally make the setup easier. If you're familiar
at all with the LSP and Vim world, you'll notice that there are a &lt;em&gt;ton&lt;/em&gt; of
options. At the time users were split between
&lt;a href=&quot;https://github.com/natebosch/vim-lsc/&quot;&gt;vim-lsc&lt;/a&gt;,
&lt;a href=&quot;https://github.com/prabirshrestha/vim-lsp&quot;&gt;vim-lsp&lt;/a&gt;,
&lt;a href=&quot;https://github.com/neoclide/coc.nvim&quot;&gt;coc.nvim&lt;/a&gt;,
&lt;a href=&quot;https://github.com/Shougo/deoplete.nvim&quot;&gt;deoplete&lt;/a&gt;,
&lt;a href=&quot;https://github.com/dense-analysis/ale&quot;&gt;Ale&lt;/a&gt;,
&lt;a href=&quot;https://github.com/autozimu/LanguageClient-neovim&quot;&gt;LanguageClient-neovim&lt;/a&gt;,
and &lt;a href=&quot;https://github.com/ycm-core/YouCompleteMe&quot;&gt;YouCompleteMe&lt;/a&gt;. The crazy part
is, I'm probably forgetting some. There are a surprising amount of options for
those looking for LSP support in Vim. I played around with a few of these,
and to be honest, when I wanted to start the plugin, it would have been a toss
up which I started with had it not been for the fact that coc.nvim provided a
way to port over VS Code extensions to be used for Vim. Ultimately, if someone
asks, why is the main Metals Vim plugin made for coc.nvim, the answer is because
we can share a core with the VS Code plugin. You can even see this in the
&lt;a href=&quot;https://github.com/scalameta/metals-languageclient&quot;&gt;metals-languageclient&lt;/a&gt;
library that &lt;a href=&quot;https://twitter.com/gabro27&quot;&gt;Gabriele Petronella&lt;/a&gt; spearheaded that
both &lt;a href=&quot;https://github.com/scalameta/coc-metals&quot;&gt;coc-metals&lt;/a&gt; and
&lt;a href=&quot;https://github.com/scalameta/metals-vscode&quot;&gt;metals-vscode&lt;/a&gt; use. The base of
coc-metals was created pretty quickly thanks to how easy coc.nvim makes it to
port over extensions. As things were coming together, I kept getting more
excited, and I really wanted the experience for Vim Metals users to be both
smooth and feature rich. I even made it a &amp;quot;resolution&amp;quot; for the new years, ha.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/vim-tweet.png&quot; alt=&quot;tweet about Vim&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-current-state-of-coc-metals&quot; id=&quot;the-current-state-of-coc-metals&quot;&gt;The current state of coc-metals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the past 6 months there has been a ton of work done on coc-metals that I'm
really proud of. With the help of multiple contributors every Metals &lt;a href=&quot;https://scalameta.org/metals/docs/editors/overview.html#metals-extensions&quot;&gt;LSP
extension&lt;/a&gt;
is supported and there is a seamless way to update your server version, restart the
server, and continue to code, all without closing your buffer. With the latest
release of coc-metals you can now debug, run, and test your code all within Vim
(Nvim only at the moment) with the help of
&lt;a href=&quot;https://github.com/puremourning/vimspector&quot;&gt;vimspector&lt;/a&gt;. You have a new project
creator, worksheets that utilize virtual text (Nvim only), and if you want to
use the Metals Tree View to explore the API of your dependencies, that's doable.
It's great to see the full feature set of Metals supported in Vim if you choose
to go the coc-metals route.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/glamour-shot.png&quot; alt=&quot;Metals and Vim&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-double-edged-sword&quot; id=&quot;the-double-edged-sword&quot;&gt;The double edged sword&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Looking back at the past 6 months, usage of coc.nvim has also skyrocketed. When
I first started looking to making a coc plugin, the project had around 6k stars.
Less than a year before that, they were less than 1k. Right now they are sitting
around almost 12k stars on GitHub. coc.nvim is a success story. They put in the
work, created an ecosystem of plugins, and capitalized on the ability of users
to port popular language plugins from VS Code over to Vim.&lt;/p&gt;
&lt;p&gt;I can't find the quote now, but a while back I read a comment on Reddit about
the worry of coc.nvim taking over your terminal and basically becoming a
coc.nvim editor powered by Vim. You can see inklings of this when you realize
they have their own way to manage plugins, an ecosystem that heavily relies on
Node, which is a bit out of the ordinary for a terminal based editor, and when
you use coc.nvim, you sort of get locked into the coc.nvim way of doing things.&lt;/p&gt;
&lt;p&gt;This is where I'll become a bit opinionated, but in my opinion this all breaks a
bit away from the experience I want with Vim, the ability to swap out components
and choose how X, Y, or Z is done. I'm a big advocate of the Linux philosophy of
&lt;em&gt;Each Program Does One Thing Well&lt;/em&gt;, and I like to be able to choose that
&lt;em&gt;program&lt;/em&gt;. For example, coc.nvim has it's own fzf alternative, there is talk of
a built-in symbol explorer, and they basically provide a full experience for
everything you'd want in your LSP editing experience if you so choose to use it.
This is &lt;em&gt;huge&lt;/em&gt; strength, but also &lt;em&gt;giant&lt;/em&gt; weakness for other users. For
most developers that dabble in Javascript, they'll have Node installed. More
than likely even if they don't they'll have Node installed, but it's still
something I hear people wish didn't have to be a dependency to use coc-metals.&lt;/p&gt;
&lt;p&gt;Overall, I'm very happy with coc-metals, and it's the way I recommend almost
anyone to use Metals with Vim. However, there are some more recent things that
get me excited and that I'll be shifting my focus and effort to this second half
of the year.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#nvim-metals&quot; id=&quot;nvim-metals&quot;&gt;nvim-metals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/nvim-metals.png&quot; alt=&quot;nvim-metals&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I'm a happy &lt;a href=&quot;https://neovim.io/&quot;&gt;neovim&lt;/a&gt; user, and I encourage anyone to take a
look at the &lt;a href=&quot;https://neovim.io/charter/&quot;&gt;Vision and Goals&lt;/a&gt; of the project. Even
with coc-metals, I think it greatly enhances your experience with the way it
handles messages, offers virtual text which unlocks the &lt;a href=&quot;https://scalameta.org/metals/docs/editors/decoration-protocol.html&quot;&gt;decoration
protocol&lt;/a&gt;,
and just offers an overall smoother experience. Apart from that, one of the main
things that get me excited is that the Nvim v.0.5.0 release will have built-in
LSP support. This is ideal and the way Nvim is approaching LSP is to have a
solid core that offers a myriad of extensibility without providing all the shiny
bells and whistles out of the gate. Much of the integration is call-back based
which allows users and plugin authors to define custom callbacks for basically
all of the functionality that you'd want in your LSP experience. This has
already worked great with the emergence of new plugins like
&lt;a href=&quot;https://github.com/nvim-lua/completion-nvim&quot;&gt;completion-nvim&lt;/a&gt; and
&lt;a href=&quot;https://github.com/nvim-lua/diagnostic-nvim&quot;&gt;diagnostic-nvim&lt;/a&gt; which greatly
enhance the Nvim LSP experience. The future for native LSP support in Nvim looks
really bright in my opinion with plugins written in Lua and everything being
easily extendable or swapped out, and that's the experience I'm looking for. It
feels much closer to the mindset of &lt;em&gt;Each Program Does One Thing Well&lt;/em&gt; like I
mentioned above without one taking over your entire system. It was for this
reason that I started &lt;a href=&quot;https://github.com/scalameta/nvim-metals&quot;&gt;nvim-metals&lt;/a&gt;
which at the moment serves and an example of how to setup Metals with the native
LSP support in Nvim, and also provides some extra functionality that you'll need
at the moment. Keep in mind that this is all changing rapidly, stuff is
breaking, and there is no stable for really any of this. However, I'm very
excited about it, and I envision myself fully switching from coc-metals to
nvim-metals in the next 6 months. It will take more work to set up, especially
in its current state, but eventually I believe it will provide a faster, more
stable, and more extensible Metals + Nvim experience.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#an-ode-to-metals&quot; id=&quot;an-ode-to-metals&quot;&gt;An Ode to Metals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd be amiss in this not to mention that Metals does a fantastic job of allowing
editors of all kinds and all levels of support to have a solid editing
experience. It's something I personally work hard at to ensure that whatever
type of client is hooking up to Metals has a positive experience with the setup
and configuration. It sort of goes unseen, but there is a lot of work that has
gone into Metals to ensure that that all sorts of clients with all levels of
support are taken care of. A few examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does the client support snippets in completions?&lt;/li&gt;
&lt;li&gt;Does the client have the ability to use virtual text or should it be given
comments in worksheets?&lt;/li&gt;
&lt;li&gt;Does the client have the ability to show Doctor results, or should Metals
take care of launching it in the browser for you? You can choose html or
json for this.&lt;/li&gt;
&lt;li&gt;Plus many more...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's because of this that an editor like Nvim with growing LSP support can still
provide a great experience with Metals -- how well configuring the server works
and also how open the team is to making necessary changes to ensure all editors
are having a good experience. I couldn't be more proud to be part of the team.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-rest-of-the-ecosystem&quot; id=&quot;the-rest-of-the-ecosystem&quot;&gt;The rest of the &lt;em&gt;Ecosystem&lt;/em&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd also like to touch a bit on the Vim and Scala ecosystem outside of Metals. I
mentioned the &lt;a href=&quot;https://github.com/derekwyatt/vim-scala&quot;&gt;vim-scala&lt;/a&gt; plugin
earlier by Derek Wyatt. Every Scala developer that has used Vim to edit any
Scala owes him a high five. The default syntax highlighting that actually ships
with Vim is from that plugin. With that being said, I don't actually recommend
usage of that plugin currently mainly because I think tooling in Scala has grown
to a point where it offers everything that the plugin can do via
&lt;a href=&quot;https://scalameta.org/scalafmt/&quot;&gt;Scalafmt&lt;/a&gt; or
&lt;a href=&quot;https://github.com/scalacenter/scalafix&quot;&gt;Scalafix&lt;/a&gt;. Organizing imports in an
intelligent way was a huge piece of the missing puzzle for me as a Metals user
until the recent
&lt;a href=&quot;https://github.com/liancheng/scalafix-organize-imports&quot;&gt;scalafix-organize-imports&lt;/a&gt;
rule by &lt;a href=&quot;https://twitter.com/liancheng&quot;&gt;Cheng Lian&lt;/a&gt; was released. This is a
godsend.&lt;/p&gt;
&lt;p&gt;Another semi-related thing to this that excites me is the future of alternative
syntax highlighting systems like
&lt;a href=&quot;https://tree-sitter.github.io/tree-sitter/&quot;&gt;tree-sitter&lt;/a&gt;. The great part is
that a &lt;a href=&quot;https://github.com/neovim/neovim/issues/11724&quot;&gt;native tree-sitter
module&lt;/a&gt; is also planed for the
next major release of Nvim. I've recently been looking into the Scala grammar
for tree-sitter, and I'm hopeful that the future holds the ability to use
tree-sitter for syntax highlight for Scala in Nvim. I think this will be a game
changer.&lt;/p&gt;
&lt;p&gt;This article is radically incomplete since I know there are users out there that
use Metals with some of the other Vim LSP clients, and maybe even some without
any client and just plane old vanilla Vim. However, I wanted to give a bit of
insight into the creation of coc-metals, the start of nvim-metals, and a bit of
context to how I see the Scala and Vim experience. I hope to continually work at
improving this, and hopefully by the end of the year I'll hear more people say,
Scala support in Vim is fantastic. That's my goal anyways.&lt;/p&gt;
&lt;p&gt;Cheers, and thanks for reading.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/scala-and-vim-part-1</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/scala-and-vim-part-1</guid><pubDate>Thu, 2 Jul 2020 00:00:00 +0200</pubDate></item><item><title>A Bloop Tour for Metals users</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;An overview of Bloop, especially relevant for Metals users.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;A Bloop Tour for Metals users&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;An overview of Bloop, especially relevant for Metals users.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;A Bloop Tour for Metals users&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#a-bloop-tour-for-metals-users&quot; id=&quot;a-bloop-tour-for-metals-users&quot;&gt;A Bloop Tour for Metals users&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Many of the common questions I see around the Metals ecosystem have to do with
Bloop. Questions like '&lt;em&gt;What is Bloop?&lt;/em&gt;', '&lt;em&gt;What's Bloop's role in Metals?&lt;/em&gt;',
and '&lt;em&gt;What does the Bloop CLI do?&lt;/em&gt;' are somewhat commonplace. If we step outside
of the Metals world, the mystery is even more enhanced. I've talked to seasoned
Scala devs before that weren't aware of Bloop. All this makes it a perfect
candidate for this mini-series on under-appreciated Scala tools after my last
&lt;a href=&quot;/blog/useful-cs-commands&quot;&gt;Coursier post&lt;/a&gt;. I don't pretend to be an expert in
Bloop at all, but I've been lucky enough to work closely with those that are and
have hopefully been able to pick up enough to help spread the word, especially
in relation to &lt;a href=&quot;https://scalameta.org/metals/&quot;&gt;Metals&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#what-is-bloop&quot; id=&quot;what-is-bloop&quot;&gt;What is Bloop&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First things first, taken in part from the &lt;a href=&quot;https://scalacenter.github.io/bloop/docs/what-is-bloop&quot;&gt;Bloop
website&lt;/a&gt;: Bloop is a
build server and CLI tool for the Scala programming language built by the &lt;a href=&quot;https://scala.epfl.ch/&quot;&gt;Scala
Center&lt;/a&gt; and spearheaded by &lt;a href=&quot;https://twitter.com/jvican&quot;&gt;Jorge Vicente
Cantero&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/mnduhem&quot;&gt;Martin
Duhem&lt;/a&gt;. Bloop has two main goals:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compile, test, and run Scala code as fast as possible&lt;/li&gt;
&lt;li&gt;Integrate easily with build tools, command-line applications, editors, and
custom tooling&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While those two points are quite clear, there are a couple other points worth
noting to help with the general understanding of Bloop. Firstly, Bloop is a
server, and like other servers, it responds to requests from &lt;em&gt;clients&lt;/em&gt;. These
clients can be something like Metals, communicating with Bloop via the &lt;a href=&quot;https://github.com/build-server-protocol/build-server-protocol&quot;&gt;Build
Server Protocol
(BSP)&lt;/a&gt;, or Bloop
CLI, which is communicating with Bloop via the &lt;a href=&quot;https://github.com/facebook/nailgun&quot;&gt;Nailgun server
protocol&lt;/a&gt;. They can be happening
concurrently, caching compilations across different clients, and offering client
isolation to avoid conflicts in a shared, stateful file system.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://scalacenter.github.io/bloop/docs/what-is-bloop&quot;&gt;&lt;em&gt;What is Bloop&lt;/em&gt;&lt;/a&gt;
portion of the website does a great job at outlining in more detail what I have
listed above, along with some of the design goals of the project.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#metals-and-bloop&quot; id=&quot;metals-and-bloop&quot;&gt;Metals and Bloop&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you've seen any of the presentations done about Metals, then you've probably
seen a graphic similar to what I have below. However, the one below is a bit
more simplified showing only one language client and one build tool.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/diagram.png&quot; alt=&quot;BSP + LSP diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This diagram is meant to show Bloop's part in the flow from build definition to
your editor of choice. If you start on the left, you have your build definition
in sbt for example. If you've used Metals before, you'll notice that when you
first open up a project you'll be prompted to &lt;em&gt;Import Your Build&lt;/em&gt;. What does
this mean? The first time this happens, it means that Metals detects that you
have no &lt;code&gt;.bloop&lt;/code&gt; directory and therefore you need to import your build. When
using sbt this means that Metals actually adds the &lt;code&gt;sbt-bloop&lt;/code&gt; plugin to your
build in &lt;code&gt;project/metals.sbt&lt;/code&gt;. It then issues an &lt;code&gt;sbt bloopInstall&lt;/code&gt; command
which will dump out your build definition for all of your modules into JSON
files. You can see this if you open up your &lt;code&gt;.bloop&lt;/code&gt; directory in your
workspace. These files contain things like your directory information, what
dependencies your module has on other modules, all your classpath information,
and more. Go ahead and take a look at everything in there. Once this information
is gathered, Metals tells Bloop to compile your project.&lt;/p&gt;
&lt;p&gt;At this point, if there are any errors during compilation, diagnostics are
forwarded from Bloop to Metals and then to your client for you to see. If you
fix the diagnostic, hit save, the save event is sent to Metals which then
forwards that to Bloop to compile what has been changed. You can start to
imagine the flow from the diagram above.&lt;/p&gt;
&lt;p&gt;At this point, one of the questions you may have is how does Bloop start? Does
Metals start it? Does Metals install Bloop? There can only be one Bloop server
running on a machine. So when Metals is about to start, one of two things
happens. Using &lt;a href=&quot;https://scalacenter.github.io/bloop/docs/launcher-reference&quot;&gt;Bloop
Launcher&lt;/a&gt; either a
Bloop server is detected and running, which Metals connects to, or the launcher
starts one.&lt;/p&gt;
&lt;p&gt;If you've ever used the debugging features in Metals, you have also utilized the
DAP (&lt;a href=&quot;https://microsoft.github.io/debug-adapter-protocol/&quot;&gt;Debug Adaptor
Protocol&lt;/a&gt;) support that
Bloop offers. You can find the entire &lt;a href=&quot;https://scalacenter.github.io/bloop/docs/debugging-reference&quot;&gt;debugging reference
here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#bloop-cli&quot; id=&quot;bloop-cli&quot;&gt;Bloop CLI&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Especially for Metals users, I highly recommend using Bloop CLI when you have a
simple workflow of compiling, testing, compiling, etc. Up above I mentioned
that compilations are cached for different clients. The power in this can be
witnessed when you're in Metals, have your project compiled, and can run a
test via the Bloop CLI and see it start basically immediately without another
compilation happening. Personally, I've fully moved from running tests through
sbt to running them through Bloop CLI simply because of how much faster it is
for me to go from executing the &lt;code&gt;bloop test &amp;lt;project&amp;gt;&lt;/code&gt; to seeing the test run.
If you run the same test through sbt you normally have both a longer startup
time and also compilation that needs to happen.&lt;/p&gt;
&lt;p&gt;Many of the things that you would imagine using like, targeting a specific test
suite, watching a test suite, passing in arguments, or testing upstream projects
all exist. However, there are a few differences and nuances worth pointing out.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#pointers&quot; id=&quot;pointers&quot;&gt;Pointers&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You can't run &lt;code&gt;bloop compile&lt;/code&gt; or &lt;code&gt;bloop test&lt;/code&gt; without specifying a project.
You need to use command substitution like below if you want to compile or
test all projects. There is no implicit root project.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ bloop compile $(bloop projects)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;By default compilation requires compilation of all downstream projects,
however, to compile upstream projects (transitive projects) that depend on
your project, use the &lt;code&gt;--cascade&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ bloop compile --cascade root
Compiling root (1 Scala source)
Compiled root (982ms)
Compiling root-test (1 Scala source)
Compiled root-test (344ms)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;If you want to clean your cache and ensure that you have all downstream
projects cleaned as well, use the &lt;code&gt;--propagate&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ bloop clean --propagate root
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When running test suites, Bloop offers completions on your suite names.
In order for this to work you need to make sure you have fully compiled
before you'll see completions offered.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;One thing to keep in mind is that the Bloop CLI doesn't fully replace the need
for sbt shell or commands. More advanced custom workflows still need to be
configured and done via sbt like publishing, packaging, and other
integrations with things like Docker, native images, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you need a repl, &lt;code&gt;bloop console&lt;/code&gt; will drop you into an &lt;a href=&quot;https://ammonite.io/&quot;&gt;Ammonite
shell&lt;/a&gt; in the targeted project.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ bloop console root
Loading...
Welcome to the Ammonite Repl 2.1.4-2-ef9b0a0 (Scala 2.12.11 Java 1.8.0_242)
@
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You can generate &lt;a href=&quot;https://graphviz.org/&quot;&gt;graphviz&lt;/a&gt; diagrams to view your
dependencies with a command like the one found below.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ bloop projects --dot-graph | dot -Tsvg -o metals-diagram.svg &amp;amp;&amp;amp; open metals-diagram.svg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/images/metals-diagram.svg&quot; alt=&quot;metals graphviz diagram&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#takeaways-and-things-to-keep-in-mind&quot; id=&quot;takeaways-and-things-to-keep-in-mind&quot;&gt;Takeaways and Things to keep in mind&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Whenever you run, test, or debug in Metals, it's all being powered by Bloop.&lt;/li&gt;
&lt;li&gt;Compilations are cached. Consider using the Bloop CLI rather than your build
tool to run your tests. You can actually witness this &lt;em&gt;compile
deduplication&lt;/em&gt; if you are watching a test with &lt;code&gt;-w&lt;/code&gt; and also compiling a
project with Metals. You'll see a message like this in your bloop output:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Deduplicating compilation of root from bsp client 'Metals 0.9.0+139-c169b4ce-SNAPSHOT' (since 39.439s)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The Bloop CLI &lt;a href=&quot;https://scalacenter.github.io/bloop/docs/cli/tutorial&quot;&gt;portion of the
website&lt;/a&gt; has a great
tutorial that goes through usage of the tool, all the flags, etc. It's a
great guide to get started with if you haven't used the Bloop CLI at all
yet, or want to freshen up on all the available flags.&lt;/li&gt;
&lt;li&gt;There are &lt;em&gt;many&lt;/em&gt; integrations with Bloop including &lt;a href=&quot;https://www.scala-sbt.org/&quot;&gt;sbt&lt;/a&gt;, &lt;a href=&quot;https://github.com/lihaoyi/mill&quot;&gt;Mill&lt;/a&gt;,
&lt;a href=&quot;https://maven.apache.org/&quot;&gt;Maven&lt;/a&gt;, &lt;a href=&quot;https://gradle.org/&quot;&gt;Gradle&lt;/a&gt;, and more.
Take some time to check them all out, and appreciate the breadth of tools
that rely on Bloop.&lt;/li&gt;
&lt;li&gt;Keep in mind that if Metals starts Bloop, when you close Metals, it doesn't
shut down the Bloop server. This is intentional. If you want to shut it
down, you'll need to run a &lt;code&gt;bloop exit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Just to make the distinction, worksheets rely on
&lt;a href=&quot;https://scalameta.org/mdoc/&quot;&gt;mdoc&lt;/a&gt; and the recently merged in Ammonite
support relies on &lt;a href=&quot;https://ammonite.io/&quot;&gt;Ammonite&lt;/a&gt;. Neither of these use
Bloop. So if you have an issue with either of them, blowing away your
&lt;code&gt;.bloop&lt;/code&gt; directory won't help.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use Bloop daily, and you may be using it as well without even realizing it.
It's an incredible tool that has set the bar for how we run, compile, and test
Scala code. If you haven't yet, take some time and head on over to the &lt;a href=&quot;https://scalacenter.github.io/bloop/&quot;&gt;Bloop
webiste&lt;/a&gt;, give the Bloop CLI a try, and
tell a friend or colleague about it.&lt;/p&gt;
&lt;p&gt;A special thanks to &lt;a href=&quot;https://twitter.com/olafurpg&quot;&gt;Ólafur&lt;/a&gt; for reading this over
and always providing valuable feedback.&lt;/p&gt;
&lt;p&gt;Thanks for stopping by.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/a-bloop-tour-for-metals-users</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/a-bloop-tour-for-metals-users</guid><pubDate>Sun, 24 May 2020 00:00:00 +0200</pubDate></item><item><title>Useful cs commands</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;&amp;quot;&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Useful cs commands&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;&amp;quot;&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Useful cs commands&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#useful-coursier-commands-for-every-dev&quot; id=&quot;useful-coursier-commands-for-every-dev&quot;&gt;Useful Coursier commands for every dev&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The Scala ecosystem has a lot of incredible tools, and for some reason I feel
like the we do a poor job at highlighting them as well as showing examples of how
useful they can be to us on a daily basis. One of these tools that I believe
deserves more attention than it does is &lt;a href=&quot;https://get-coursier.io/&quot;&gt;Coursier&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#coursier&quot; id=&quot;coursier&quot;&gt;Coursier&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I often hear people mention Coursier mainly when they're talking about
&lt;a href=&quot;https://www.scala-sbt.org/&quot;&gt;sbt&lt;/a&gt;, and how as of sbt 1.3, it ships with Coursier
out of the box and utilizes it to fetch dependencies. While that's where the
knowledge of Coursier ends for some, I want to show a few use cases of Coursier
that I think are both simple and extremely useful for daily things we do as
Scala devs.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#coursier-cli&quot; id=&quot;coursier-cli&quot;&gt;Coursier CLI&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Over the past couple of weeks I've noticed my usage of Coursier CLI spike as I've
been either shown or come across new &lt;code&gt;cs&lt;/code&gt; commands. &lt;code&gt;cs&lt;/code&gt; is the native launcher
for Coursier. You can find information on how to install it
&lt;a href=&quot;https://get-coursier.io/docs/cli-overview&quot;&gt;here&lt;/a&gt; along with a nice overview of
the few commands I'll show in this post and some others I won't cover. Again,
my goal for this isn't to do an in-depth dive into all the ways you can use
&lt;code&gt;cs&lt;/code&gt; but to give you some practical examples with a few commands that you should
be able to start using right away. The following examples are all real scenarios
that I came across in the last couple weeks.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#resolve&quot; id=&quot;resolve&quot;&gt;resolve&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While at work, I was in a conversation in a pull request about how my team didn't
love the Java Api of &lt;a href=&quot;https://github.com/ben-manes/caffeine&quot;&gt;Caffeine&lt;/a&gt;, which we
were using for some caching. We came across
&lt;a href=&quot;https://github.com/blemale/scaffeine&quot;&gt;Scaffeine&lt;/a&gt;, which is a thin Scala wrapper
around the library. Great! However, we try to be mindful of how many
dependencies we add, and the question came up of how many transitive
dependencies we'd be bringing in by including this. So the question becomes:
what is the easiest way to check this? Enter &lt;code&gt;cs resolve&lt;/code&gt;. The &lt;code&gt;resolve&lt;/code&gt; command
is a quick way to print out transitive dependencies of one or more other
dependencies. So, I did the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ cs resolve com.github.blemale:scaffeine_2.12:3.1.0
https://repo1.maven.org/maven2/com/github/blemale/scaffeine_2.12/3.1.0/scaffeine_2.12-3.1.0.pom
  100.0% [##########] 2.6 KiB (7.6 KiB / s)
https://repo1.maven.org/maven2/org/scala-lang/scala-library/2.12.8/scala-library-2.12.8.pom
  100.0% [##########] 1.6 KiB (26.0 KiB / s)
https://repo1.maven.org/maven2/org/scala-lang/modules/scala-java8-compat_2.12/0.9.0/scala-java8-compat_2.12-0.9.0.pom
  100.0% [##########] 2.3 KiB (14.4 KiB / s)
https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.8.0/caffeine-2.8.0.pom
  100.0% [##########] 5.2 KiB (25.8 KiB / s)
https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.3.3/error_prone_annotations-2.3.3.pom
  100.0% [##########] 1.7 KiB (29.5 KiB / s)
https://repo1.maven.org/maven2/org/checkerframework/checker-qual/2.10.0/checker-qual-2.10.0.pom
  100.0% [##########] 2.3 KiB (39.7 KiB / s)
https://repo1.maven.org/maven2/com/google/errorprone/error_prone_parent/2.3.3/error_prone_parent-2.3.3.pom
  100.0% [##########] 5.2 KiB (62.5 KiB / s)
com.github.ben-manes.caffeine:caffeine:2.8.0:default
com.github.blemale:scaffeine_2.12:3.1.0:default
com.google.errorprone:error_prone_annotations:2.3.3:default
org.checkerframework:checker-qual:2.10.0:default
org.scala-lang:scala-library:2.12.8:default
org.scala-lang.modules:scala-java8-compat_2.12:0.9.0:default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the full artifacts weren't fetched, only the metadata necessary
to be able to list the transitive dependencies, which can be found at the
bottom. You can also get a nice tree view of this by passing in the &lt;code&gt;--tree&lt;/code&gt; or
&lt;code&gt;-t&lt;/code&gt; option.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ cs resolve -t com.github.blemale:scaffeine_2.12:3.1.0
  Result:
└─ com.github.blemale:scaffeine_2.12:3.1.0
   ├─ com.github.ben-manes.caffeine:caffeine:2.8.0
   │  ├─ com.google.errorprone:error_prone_annotations:2.3.3
   │  └─ org.checkerframework:checker-qual:2.10.0
   ├─ org.scala-lang:scala-library:2.12.8
   └─ org.scala-lang.modules:scala-java8-compat_2.12:0.9.0
      └─ org.scala-lang:scala-library:2.12.4 -&amp;gt; 2.12.8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is such a quick way to be able to see the dependency tree of a dependency
without adding it into your build or looking through build files.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#install&quot; id=&quot;install&quot;&gt;install&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another useful command is the &lt;code&gt;cs install&lt;/code&gt; command which allows you to install a
launcher for a JVM-based application. The
&lt;a href=&quot;https://get-coursier.io/docs/cli-install&quot;&gt;docs&lt;/a&gt; do a great job at explaining
how this all works, but think of &lt;code&gt;cs install&lt;/code&gt; as an &lt;code&gt;apt install&lt;/code&gt; or &lt;code&gt;brew install&lt;/code&gt;
alternative for JVM-based apps. You can install, update, and uninstall apps that
you want to use locally just like you would with &lt;code&gt;&amp;lt;enter global package manager name here&amp;gt;&lt;/code&gt;. For my situation, I'm a big fan of the
&lt;a href=&quot;https://scalameta.org/mdoc/&quot;&gt;mdoc&lt;/a&gt; tool, but I normally use it when
it's included in a project already. For this situation I just wanted to test
something locally without including it in the project. I was reviewing an
&lt;code&gt;.adoc&lt;/code&gt; document and was curious if mdoc supported them the same way it does
&lt;code&gt;.md&lt;/code&gt; files. So I simply did the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ cs install mdoc
...
...
...
Wrote mdoc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This then allowed me full access to the &lt;code&gt;mdoc&lt;/code&gt; cli commands, which I could use
locally without having to include it in my project to test something out.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#complete&quot; id=&quot;complete&quot;&gt;complete&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Speaking of mdoc, I was using it in another project, and I was curious what the
latest version was. I was actually using it in an
&lt;a href=&quot;https://ammonite.io/&quot;&gt;Ammonite&lt;/a&gt; script (to create this website), so I didn't
have access to the &lt;code&gt;sbt dependencyUpdates&lt;/code&gt; command that I would typically use. I
ended up looking on GitHub for the version, which lead me to the site, which
lead me to the version. Ironically, I sent in a pr to add in a badge to the
readme to make the latest version more discoverable when the author of mdoc,
&lt;a href=&quot;https://twitter.com/olafurpg&quot;&gt;Ólafur Páll Geirsson&lt;/a&gt;, introduced me to this gem:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;❯ cs complete org.scalameta:mdoc_2.13:
1.3.2
1.3.4
1.3.5
1.3.6
1.4.0-RC2
1.4.0-RC3
2.0.0
2.0.1
2.0.2
2.0.3
2.1.0
2.1.1
2.1.2
2.1.3
2.1.4
2.1.5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cs complete&lt;/code&gt; isn't actually on the Coursier website, but you can use it to give
you all the options to complete the artifact that you are looking for. This
provides such a simple way to explore what versions are cross published and
available.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#tell-your-friends&quot; id=&quot;tell-your-friends&quot;&gt;Tell your friends&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I want re-iterate that I think there are a ton of great tools in the Scala
ecosystem, but not everyone is aware of them. Hopefully, the few real-life
examples above illustrate this in a small way. Next time someone mentions
Coursier, ask them if they've used any of the &lt;code&gt;cs&lt;/code&gt; commands, and show them a
couple tips. Let's spread the word about the great tools we have access to.&lt;/p&gt;
&lt;p&gt;Cheers.&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/useful-cs-commands</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/useful-cs-commands</guid><pubDate>Sat, 4 Apr 2020 00:00:00 +0200</pubDate></item><item><title>How is this site built</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;A short overview of how this site is built.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;How is this site built&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;A short overview of how this site is built.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;How is this site built&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#how-is-this-site-built&quot; id=&quot;how-is-this-site-built&quot;&gt;How is this site built?&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;If you just want to know exactly how this site is built and don't care about the
history, skip down to the &lt;a href=&quot;#the-current-process&quot;&gt;current-process&lt;/a&gt; section. If
not, I'll take this opportunity to actual outline the different sites that I've
had along the way leading up to this one as they've radically changed along the
way.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#version-1&quot; id=&quot;version-1&quot;&gt;Version 1&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I originally had a site that was fully JavaScript powered, and built from
scratch. I even created a tiny micro-framework that I used to build it called
&lt;a href=&quot;https://github.com/ckipp01/gyul&quot;&gt;gyul&lt;/a&gt;. I had a lot of fun with that build, but
it was a bit slow on initial load and wouldn't work for visitors that had JS
disabled. However I look back at it fondly since it was my first time truly
building something that was mine, and it also got me away from using other
blogging platforms. This also corresponded with a project I started called
&lt;code&gt;ándaga&lt;/code&gt; which was a way for me to track my own free-time to try and find
patterns. Everything was integrated.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#version-2&quot; id=&quot;version-2&quot;&gt;Version 2&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As I got more into Scala, I wanted to see if I could generate my site using that
instead of JS. This lead me to a collection of tools and a process that I'll
outline below.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a log via &lt;a href=&quot;https://github.com/ckipp01/andaga&quot;&gt;ándaga-cli&lt;/a&gt; for a task
that I was working on.&lt;/li&gt;
&lt;li&gt;Log gets stored into &lt;a href=&quot;https://www.mongodb.com/&quot;&gt;MongoDB&lt;/a&gt; by
&lt;a href=&quot;https://github.com/ckipp01/andaga-core&quot;&gt;ándaga-core&lt;/a&gt;, which are some
serverless functions that were running on Vercel&lt;/li&gt;
&lt;li&gt;Nightly, I had a script that ran which dumped my DB into a JSON file. This
JSON got committed and pushed to the
&lt;a href=&quot;https://github.com/ckipp01/chronica&quot;&gt;chronica&lt;/a&gt; repo, which was the repo of
my website.&lt;/li&gt;
&lt;li&gt;The push triggered GitHub Actions to run, which ran
&lt;a href=&quot;https://ammonite.io&quot;&gt;Ammonite&lt;/a&gt;, &lt;a href=&quot;https://scalameta.org/mdoc/&quot;&gt;mdoc&lt;/a&gt;, and
&lt;a href=&quot;https://github.com/atlassian/commonmark-java&quot;&gt;CommonMark&lt;/a&gt; to transform and
enrich the markdown into html.&lt;/li&gt;
&lt;li&gt;After building the created html pages got uploaded and hosted on
&lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A similar process happened when I add a blog post and push it up manually&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This was also a really fun project to set up and learn more about various tools.
However, it was also a bit of a hassle to manage if I ever wanted to change
anything. It was sort of like I built the entire system, and then if I wanted to
change something, the entire system needed to change. During this time I also
stopped logging my free time, which is a whole other story, and also just wanted
to simplify the process further. That's what lead me to my current process.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-current-process&quot; id=&quot;the-current-process&quot;&gt;The current process&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The way that the site is currently built is quite simple. It just uses
&lt;a href=&quot;https://pandoc.org&quot;&gt;Pandoc&lt;/a&gt; to transform my markdown pages you see in the repo
into html pages. I have some custom html partials that include the various parts
of the site and the style. The entire thing is just built with &lt;a href=&quot;https://github.com/ckipp01/chris-kipp.io/blob/main/bin/make-site.sh&quot;&gt;this
script&lt;/a&gt; via
&lt;a href=&quot;https://github.com/ckipp01/chris-kipp.io/actions&quot;&gt;GitHub Actions&lt;/a&gt;, and then
automatically deployed to &lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt;. So far I've found that
this site is incredibly fast and easy to manage. Pandoc is also a joy to work
with. That's pretty much it, and that's all I really want.&lt;/p&gt;
&lt;p&gt;Thanks for stopping by.&lt;/p&gt;
&lt;p&gt;Chris&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/how-is-this-site-built</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/how-is-this-site-built</guid><pubDate>Sun, 23 Feb 2020 00:00:00 +0100</pubDate></item><item><title>Why my own blog</title><description>&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot; class=&quot;site-root&quot;&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;&lt;meta name=&quot;description&quot; content=&quot;This post gives some insight into why I have my own blog and I prefer to not use a site like Medium.&quot; /&gt;&lt;meta name=&quot;keywords&quot; content=&quot;Chris Kipp, ckipp01, ckipp, Scala, Developer Tooling&quot; /&gt;&lt;meta name=&quot;thumbnail&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:type&quot; content=&quot;website&quot; /&gt;&lt;meta name=&quot;og:title&quot; content=&quot;Why my own blog&quot; /&gt;&lt;meta name=&quot;og:image&quot; content=&quot;../images/me-cream.png&quot; /&gt;&lt;meta name=&quot;og:description&quot; content=&quot;This post gives some insight into why I have my own blog and I prefer to not use a site like Medium.&quot; /&gt;&lt;link rel=&quot;icon&quot; href=&quot;../images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&gt;&lt;title&gt;Why my own blog&lt;/title&gt;&lt;style&gt;* {
  margin:0;
  padding:0;
  border:0;
  outline:0;
  border-spacing:0;
  text-decoration:none;
  font-weight:inherit;
  font-style:inherit;
  color:inherit;
  font-size:100%;
  font-family:inherit;
  vertical-align:baseline;
  list-style:none;
  border-collapse:collapse;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  }

  @import url('https://rsms.me/inter/inter.css');
  html { font-family: 'Inter', sans-serif; }
  @supports (font-variation-settings: normal) {
    html { font-family: 'Inter var', sans-serif; }
  }

*:focus {
  background: rgb(106, 112, 110);
}&lt;/style&gt;&lt;style&gt;.site-root{
  font-size: 18px;
  min-height: 100%;
}

.site-bodyBase{
  background: rgb(252, 243, 217);
  height: 100vh;
}
.site-bodyBase i{
  font-style: italic;
}
.site-bodyBase b{
  font-weight: bold;
}
.site-bodyBase hr{
  margin-bottom: 15px;
  margin-top: 15px;
}
.site-bodyBase main{
  line-height: 1.75rem;
  margin: 10px auto;
  max-width: 750px;
  padding: 10px;
}

.site-linkBase a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}

.site-linkHoverBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-headerBase{
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 5px;
}
.site-headerBase a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-headerBase nav{
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
.site-headerBase nav a{
  font-size: 1.75rem;
  margin: 5px;
  padding: 5px;
}

.site-headings h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-headings h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}

.site-tableBase table{
  margin: 5px 5px 15px 5px;
  padding: 5px;
}
.site-tableBase table tr{
  border-bottom: 1px solid;
}
.site-tableBase th{
  font-weight: bold;
  margin: 5px;
  padding: 5px;
}
.site-tableBase td{
  margin: 5px;
  padding: 5px;
}

.site-footerBase{
  display: flex;
  justify-content: center;
  margin: 5px 0;
}
.site-footerBase a{
  width: 40px;
}
.site-footerBase a:hover{
  background: inherit;
  color: inherit;
  transform: scale(1.2);
}
.site-footerBase img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-activePage{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-iconContainer{
  display: flex;
  justify-content: center;
}
.site-iconContainer a{
  width: 40px;
}
.site-iconContainer img{
  margin: 5px 0;
  max-height: 20px;
  padding: 10px;
}

.site-scaleOnHover:hover{
  transform: scale(1.1);
}

.site-maxAndCenter{
  margin: 5px auto;
  max-width: 750px;
}

.site-largeFontOverview{
  margin-bottom: 20px;
}
.site-largeFontOverview span{
  font-style: italic;
  opacity: 0.5;
}
.site-largeFontOverview a{
  font-size: 1.25rem;
  padding: 2px;
}
.site-largeFontOverview p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-talkListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-talkListing div{
  margin-bottom: 20px;
}
.site-talkListing span{
  font-style: italic;
  opacity: 0.5;
}
.site-talkListing p{
  font-size: 1.25rem;
  margin-bottom: 0px;
}

.site-writing h1{
  font-size: 1.5rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h2{
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 2rem;
  margin: 1rem 0;
}
.site-writing h3{
  font-size: 1.2rem;
  font-weight: bold;
  line-height: 1.5rem;
  margin: 1rem 0;
}
.site-writing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}
.site-writing a{
  border-bottom-style: dashed;
  border-bottom-width: 2px;
  border-color: rgb(0, 0, 0);
  padding: 2px;
}
.site-writing img{
  display: block;
  margin: auto;
  margin-bottom: 10px;
  max-height: 500px;
  max-width: 100%;
}
.site-writing p{
  margin-bottom: 15px;
}
.site-writing ul{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing ol{
  margin-bottom: 15px;
  margin-left: 15px;
}
.site-writing blockquote{
  border-left-color: rgb(0, 0, 0);
  border-left-style: solid;
  border-left-width: 3;
  font-size: small;
  margin-left: 5px;
}
.site-writing blockquote p{
  line-height: 1.25rem;
  padding-left: 5px;
}
.site-writing li{
  list-style-type: circle;
  margin-left: 20px;
}
.site-writing em{
  font-style: italic;
}
.site-writing iframe{
  display: block;
  margin: auto;
  width: 100%;
}
.site-writing pre{
  margin: 15px 0;
  overflow: auto;
}
.site-writing code{
  font-family: monospace;
  line-height: 1rem;
}
.site-writing codeblock{
  font-family: monospace;
  line-height: 1rem;
}

.site-blogListing{
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.site-blogListing a:hover{
  background: rgb(0, 0, 0);
  border-radius: 2px;
  color: rgb(252, 243, 217);
}

.site-album{
  align-items: center;
  display: flex;
  flex-direction: column;
}
.site-album img{
  max-height: 100px;
  padding: 10px;
}

.site-albumDescription{
  flex-grow: 1;
}

.site-albumRating{
  white-space: nowrap;
}

.site-star{
  height: 20px;
}

.site-article p{
  font-style: italic;
  opacity: 0.5;
}

.site-sites{
  margin-bottom: 10px;
}
.site-sites p{
  font-style: italic;
  opacity: 0.5;
}
.site-sites span{
  font-size: 0.8em;
  font-style: italic;
  opacity: 0.5;
}
&lt;/style&gt;&lt;style&gt;@media (min-width: 768px) {
  .site-album {
    flex-direction: row;
  }
}&lt;/style&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;../js/prism.js&quot;&gt;&lt;/script&gt;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;../css/prism.css&quot; /&gt;&lt;/head&gt;&lt;body class=&quot;site-bodyBase&quot;&gt;&lt;header class=&quot;site-headerBase&quot;&gt;&lt;nav&gt;&lt;a href=&quot;/about&quot;&gt;about&lt;/a&gt;&lt;a href=&quot;/blog&quot;&gt;blog&lt;/a&gt;&lt;a href=&quot;/lists&quot;&gt;lists&lt;/a&gt;&lt;a href=&quot;/talks&quot;&gt;talks&lt;/a&gt;&lt;a href=&quot;https://www.tooling-talks.com&quot;&gt;tooling talks podcast&lt;/a&gt;&lt;/nav&gt;&lt;/header&gt;&lt;main class=&quot;site-writing site-tableBase&quot;&gt;&lt;h1&gt;&lt;a href=&quot;#why-my-own-blog&quot; id=&quot;why-my-own-blog&quot;&gt;Why my own blog?&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;I've been meaning to start this for quite some time. Over the years I've had
various forms of blogs. I was on Tumblr for some time and then I had a Medium
page, but none of them stuck, and I only have theories of why that is. One of
the main reasons I believe is that I didn't have full control over the content.
My hope with the blog portion of chronica is that it's a place for me to dump my
ramblings, talk about my experiments, and even share some stuff I've been
working on.&lt;/p&gt;
&lt;p&gt;A while back I read a blog post, that I regretfully can't find anymore, but it
resonated more than any other blog post had for quite some time. It made a
correlation between the current state of the internet and the current state of
traveling. When you travel you are warned of the main streets. Stay away from
those shops. It's well known that they are tourist traps. You will get lower
quality for a higher price. However, if you put a bit more effort into the
finding better places to stop that may be further out, you'll have a much better
experience. The quality will better, it may even be cheaper, and it will be more
authentic. You may even find other similar places that they recommend for you to
check out.&lt;/p&gt;
&lt;p&gt;I believe we are falling for the main street of the internet. The easily
accessible &amp;quot;stores&amp;quot; and &amp;quot;shops&amp;quot; such as Facebook, Medium, and other common
social and blog outlets are by far the easiest place to digest information, but
at the cost of quality (albeit not always), and the many times the cost of
privacy. I envision the internet experience returning back to a more
decentralized platform where individuals have their own site and they own their
data. Alternatives to common social platforms do exists, they just take a bit
more effort. However, in my experience you're left with a richer experience.
Taking the time to research and to find a place a bit farther off the main
street is indeed more labor intensive, but rewarding.&lt;/p&gt;
&lt;p&gt;My hope is the that my website is just that. A central place that I fully own
and have control over where my projects, thoughts, and works can all be
displayed. There is no overly invasive trackers, no advertisements, and no
abstraction over what you are seeing. Everything is open sourced and
transparent. This is an alternative to me using platforms like Medium which
bombard you with advertisements and track as much information about you and your
readings as possible, or Facebook that caters what you see further enforcing
filter bubbles and essentially selling &lt;em&gt;you&lt;/em&gt; as their product.&lt;/p&gt;
&lt;p&gt;You can read more about how this site is build and works
&lt;a href=&quot;/blog/how-is-this-site-built&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for stopping by.&lt;/p&gt;
&lt;p&gt;Chris&lt;/p&gt;
&lt;/main&gt;&lt;footer class=&quot;site-footerBase&quot;&gt;&lt;a href=&quot;https://github.com/ckipp01&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/github.svg&quot; alt=&quot;GitHub logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;mailto:hello@chris-kipp.io&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/email.svg&quot; alt=&quot;email logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://hachyderm.io/@ckipp&quot; rel=&quot;me noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/mastodon.svg&quot; alt=&quot;mastodon logo&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://www.chris-kipp.io/rss.xml&quot;&gt;&lt;img class=&quot;site-scaleOnHover&quot; src=&quot;../images/rss.svg&quot; alt=&quot;rss icon&quot; /&gt;&lt;/a&gt;&lt;/footer&gt;&lt;/body&gt;&lt;/html&gt;</description><link>https://www.chris-kipp.io/blog/why-my-own-blog</link><author>Chris Kipp</author><category>Blog</category><guid isPermaLink="true">https://www.chris-kipp.io/blog/why-my-own-blog</guid><pubDate>Wed, 10 Jul 2019 00:00:00 +0200</pubDate></item></channel></rss>