Intro

I am a developer, full of passion to open-source community.

In a long time I spent most of my time working or hanging around. But recently, I sent several small code PRs to neovim and golang/tools that made me felt so great.

So I renewed my blog using mdbook with catpuccin theme, to write down what I have done or working on in my life.

To Find Me

You can easily find me by searching acehinnnqru in social medias, including,

Open Source

This directory would record my what I have done or learnt in open-source community.

I would orgnize it by projects.

Neovim

To project Neovim

PR: Fix opts.title Behaviors of _make_floating_popup_size

PR: neovim#33016 fixes Issue: neovim#33009

Background

I discovered this issue while browsing through the Neovim issue tracker.

Understanding the Problem

The issue reported unexpected behavior with opts.title in _make_floating_popup_size. My investigation revealed that while opts.title worked correctly with simple string values, it failed to handle the [string, string][] format (an array of text-highlight pairs).

After discussing with the issue author, I learned that vim.lsp.util.open_floating_preview internally uses vim.api.nvim_open_win, which supports both title formats according to the Neovim API specification. The bug was clear: _make_floating_popup_size needed to calculate width correctly for both title formats.

Implementation Journey

Initial Attempt

My first implementation was functional but had critical flaws:

if opts.title then
  local title = opts.title
  local title_length = 0
  if type(title) == 'string' then
    title_length = vim.fn.strdisplaywidth(title)
  else
    if title ~= nil then
      for i = 1, #title do
        title_length = title_length + string.len(title[i][1])
      end
    end
  end
  width = math.max(width, title_length)

Issues with this approach:

  1. Incorrect CJK handling: Using string.len() instead of vim.fn.strdisplaywidth() resulted in wrong width calculations for CJK (Chinese, Japanese, Korean) characters.
  2. Code complexity: The nested conditionals made the logic harder to follow and maintain.

Refined Solution

@luukvbaal suggested a much more elegant approach:

local title_length = 0
local chunks = type(opts.title) == 'string' and { { opts.title } } or opts.title or {}
for _, chunk in
  ipairs(chunks --[=[@as [string, string][]]=])
do
  title_length = title_length + vim.fn.strdisplaywidth(chunk[1])

Why this is better:

  • Unified handling: Normalizes both title formats (string and [string, string][]) into a consistent array structure
  • Cleaner logic: Eliminates nested conditionals with a concise ternary expression
  • Type safety: Uses Lua's type annotation syntax --[=[@as [string, string][]]=] to help with LSP type checking

This elegant solution taught me a new Lua pattern that I hadn't encountered before.

Testing

Before submitting for review, I added a unit test following the existing test patterns:

it('considers [string,string][] title when computing width', function()
  eq(
    { 17, 2 },
    exec_lua(function()
      return {
        vim.lsp.util._make_floating_popup_size(
          { 'foo', 'bar' },
          { title = { { 'A very ', 'Normal' }, { 'long title', 'Normal' } } }
        ),
      }
    end)
  )
end)

The test validates that the function correctly computes the width when given a title with multiple styled chunks.

Outcome

After pushing the changes and requesting review, @zeertzjq merged the PR successfully.

Key Takeaways

  1. Communication is crucial: Discussing with the issue author helped me understand the underlying API design and expected behavior.

  2. Learn from reviewers: @luukvbaal's suggestion introduced me to more idiomatic Lua patterns and the type annotation syntax for better code quality.

  3. Don't be intimidated: Contributing to large codebases like Neovim is more approachable than it seems. Starting with well-defined issues and learning from reviewer feedback makes the process manageable and educational.

PR: Set Correct '] Mark When Pasting Chunks

PR: neovim#33025 fixes Issue: neovim#32960

Background

I discovered this issue while browsing through the Neovim issue tracker.

Solution Process

Although the final fix only required a few lines of code, the implementation went through many iterations, resulting in 20+ commits during the review process.

Review Iterations

The review process turned out to be more challenging than the bug itself. After submitting my initial implementation, @zeertzjq suggested improvements focusing on the conditional logic structure. I experimented with various if branch arrangements, believing I was moving in the right direction.

When @zeertzjq brought @justinmk into the review, I was confident the code was ready to merge. However, @justinmk proposed a significantly different approach—one that, ironically, resembled my very first implementation. This sparked a cycle where I found myself toggling between different versions, each making sense from a certain perspective but lacking consensus from the reviewers.

The Key Lesson

I eventually realized my mistake: I failed to document the rationale behind my implementation choices. Without a clear explanation of why I implemented it a certain way, different reviewers naturally had different preferences and suggestions. This caused unnecessary back-and-forth discussions.

Final Implementation

Here is the merged version:

--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -213,7 +213,7 @@ end
 vim.inspect = vim.inspect

 do
-  local tdots, tick, got_line1, undo_started, trailing_nl = 0, 0, false, false, false
+  local startpos, tdots, tick, got_line1, undo_started, trailing_nl = nil, 0, 0, false, false, false

   --- Paste handler, invoked by |nvim_paste()|.
   ---
@@ -328,7 +328,13 @@ do
       -- message when there are zero dots.
       vim.api.nvim_command(('echo "%s"'):format(dots))
     end
+    if startpos == nil then
+      startpos = vim.fn.getpos("'[")
+    else
+      vim.fn.setpos("'[", startpos)
+    end
     if is_last_chunk then
+      startpos = nil
       vim.api.nvim_command('redraw' .. (tick > 1 and '|echo ""' or ''))
     end
     return true -- Paste will not continue if not returning `true`.

Key Takeaways

  1. Document your reasoning: When multiple implementation approaches are possible, always explain why you chose your specific approach. This helps reviewers understand your thought process and reduces unnecessary iteration.

  2. Be proactive in communication: If you believe your implementation is correct, provide a clear and concise summary explaining your rationale. This prevents misunderstandings and makes the review process more efficient.

Golang

To project Golang or sub projects under it.

Tools

To project Golang Tools

PR: Preserve Comments in mapsloop

PR: golang/tools#660255 fixes Issue: golang/go#72958

Background

I discovered this issue while browsing through the Go issue tracker.

Problem Analysis

The issue reported that the mapsloop analyzer was discarding comments when refactoring map operations. The root cause was straightforward: the refactoring process wasn't collecting and preserving existing comments.

Solution Approach

The fix involved collecting comments using the allComments function and inserting them before the refactored code. However, this required some code reorganization first.

Code Restructuring

The allComments function was originally defined inside the minmax analyzer. To reuse it in mapsloop, I needed to extract it to a shared location. Thanks to Alan Donovan, who reviewed the code and recommended extracting the function rather than duplicating it—a much cleaner approach that follows the DRY (Don't Repeat Yourself) principle.

Edge Case Discovery

My initial implementation collected all comments and placed them at the start node position, regardless of whether mrhs was nil. However, this caused incorrect behavior when mrhs != nil.

The issue: When mrhs != nil (i.e., when there's a preceding make assignment), the start node points to the wrong location:

m := make(map[string]string)
 ^  // start node position - too late!

Placing comments here would put them in the middle of the statement rather than before it.

The fix: Use curPrev.Node() instead, which correctly points to the node before the range statement, ensuring comments are placed before the entire refactored block.

Final Implementation

Here's the complete diff:

--- a/gopls/internal/analysis/modernize/maps.go
+++ b/gopls/internal/analysis/modernize/maps.go
@@ -156,16 +156,35 @@ func mapsloop(pass *analysis.Pass) {
                        start, end token.Pos
                )
                if mrhs != nil {
-                       // Replace RHS of preceding m=... assignment (and loop) with expression.
-                       start, end = mrhs.Pos(), rng.End()
-                       newText = fmt.Appendf(nil, "%s%s(%s)",
+                       // Replace assignment and loop with expression.
+                       //
+                       //   m = make(...)
+                       //   for k, v := range x { /* comments */ m[k] = v }
+                       //
+                       //   ->
+                       //
+                       //   /* comments */
+                       //   m = maps.Copy(x)
+                       curPrev, _ := curRange.PrevSibling()
+                       start, end = curPrev.Node().Pos(), rng.End()
+                       newText = fmt.Appendf(nil, "%s%s = %s%s(%s)",
+                               allComments(file, start, end),
+                               analysisinternal.Format(pass.Fset, m),
                                prefix,
                                funcName,
                                analysisinternal.Format(pass.Fset, x))
                } else {
                        // Replace loop with call statement.
+                       //
+                       //   for k, v := range x { /* comments */ m[k] = v }
+                       //
+                       //   ->
+                       //
+                       //   /* comments */
+                       //   maps.Copy(m, x)
                        start, end = rng.Pos(), rng.End()
-                       newText = fmt.Appendf(nil, "%s%s(%s, %s)",
+                       newText = fmt.Appendf(nil, "%s%s%s(%s, %s)",
+                               allComments(file, start, end),
                                prefix,
                                funcName,
                                analysisinternal.Format(pass.Fset, m),

The diff shows two key improvements:

  1. Comment preservation: Uses allComments(file, start, end) to collect comments from the original code
  2. Correct positioning: When mrhs != nil, uses curPrev.Node().Pos() as the start position to ensure comments are placed before the entire statement block
  3. Enhanced documentation: Adds inline comments showing the before/after transformation, making the code more maintainable

Key Takeaways

  1. Don't Repeat Yourself (DRY): When you find yourself duplicating code, extract it into a shared function. This was reinforced by Alan Donovan's review feedback—extracting allComments rather than copying it made the codebase more maintainable.

  2. Test edge cases thoroughly: My initial implementation worked for the simple case but failed when mrhs != nil. This highlighted the importance of considering all code paths and testing different scenarios.

  3. AST positioning matters: When working with Go's AST (Abstract Syntax Tree), understanding node positions is crucial. A small difference (using rng.Pos() vs curPrev.Node().Pos()) can place your changes in entirely the wrong location.

  4. Document transformations: The inline before/after examples in the code comments help future maintainers understand what the refactoring does at a glance.