Skip to content

Instantly share code, notes, and snippets.

@saiashirwad
Created March 31, 2025 19:11
Show Gist options
  • Save saiashirwad/5157b624c42636a18929b3a1ed268e5e to your computer and use it in GitHub Desktop.
Save saiashirwad/5157b624c42636a18929b3a1ed268e5e to your computer and use it in GitHub Desktop.

Let me try a completely different approach then. Let's bypass Conform and use Neovim's built-in functions to run a formatter directly on the TypeScript code:

vim.api.nvim_create_user_command('FormatTSMarkdown', function()
  -- Save cursor position
  local cursor_pos = vim.api.nvim_win_get_cursor(0)
  local bufnr = vim.api.nvim_get_current_buf()
  
  -- Find TypeScript code blocks
  local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
  local ts_start_lines = {}
  local ts_blocks = {}
  local in_ts_block = false
  local current_block = {}
  local current_start = 0
  
  for i, line in ipairs(lines) do
    if line == "```typescript" then
      in_ts_block = true
      current_start = i
      current_block = {}
    elseif line == "```" and in_ts_block then
      in_ts_block = false
      table.insert(ts_start_lines, current_start)
      table.insert(ts_blocks, {
        start = current_start,
        finish = i,
        content = current_block
      })
    elseif in_ts_block then
      table.insert(current_block, line)
    end
  end
  
  -- If no TypeScript blocks found
  if #ts_blocks == 0 then
    vim.notify("No TypeScript code blocks found", vim.log.levels.INFO)
    return
  end
  
  -- Process blocks from bottom to top to prevent line number changes
  for i = #ts_blocks, 1, -1 do
    local block = ts_blocks[i]
    
    -- Skip empty blocks
    if #block.content == 0 then
      goto continue
    end
    
    -- Create a temporary file for the TypeScript code
    local temp_file = vim.fn.tempname() .. ".ts"
    local file = io.open(temp_file, "w")
    if not file then
      vim.notify("Failed to create temporary file", vim.log.levels.ERROR)
      goto continue
    end
    
    -- Write content to the file
    file:write(table.concat(block.content, "\n"))
    file:close()
    
    -- Format the file using dprint (or another external formatter)
    local formatted_content = vim.fn.system("dprint fmt " .. vim.fn.shellescape(temp_file))
    
    -- Check if formatting succeeded
    if vim.v.shell_error ~= 0 then
      vim.notify("Failed to format TypeScript block", vim.log.levels.ERROR)
      os.remove(temp_file)
      goto continue
    end
    
    -- Read the formatted content
    file = io.open(temp_file, "r")
    if not file then
      vim.notify("Failed to read formatted file", vim.log.levels.ERROR)
      os.remove(temp_file)
      goto continue
    end
    
    local formatted_lines = {}
    for line in file:lines() do
      table.insert(formatted_lines, line)
    end
    file:close()
    
    -- Replace the original block with the formatted content
    local full_block = {"```typescript"}
    vim.list_extend(full_block, formatted_lines)
    table.insert(full_block, "```")
    
    vim.api.nvim_buf_set_lines(bufnr, block.start - 1, block.finish, false, full_block)
    
    -- Clean up
    os.remove(temp_file)
    
    ::continue::
  end
  
  -- Restore cursor position (adjusted for line changes)
  vim.api.nvim_win_set_cursor(0, cursor_pos)
  
  vim.notify("TypeScript code blocks formatted", vim.log.levels.INFO)
end, {})

If that doesn't work, let's simplify further and use Lua's string pattern matching to extract and format blocks. This approach completely bypasses Neovim's buffer manipulation until the end:

vim.api.nvim_create_user_command('FormatTSMarkdown', function()
  -- Save the current file to ensure it's on disk
  vim.cmd("write")
  
  -- Get the current file path
  local filepath = vim.fn.expand("%:p")
  
  -- Create a temporary file
  local temp_file = vim.fn.tempname()
  
  -- Use a shell command to extract, format, and reinsert TypeScript blocks
  local cmd = string.format([[
    awk '
    BEGIN { in_block = 0; block_number = 0; }
    
    /^```typescript$/ {
      in_block = 1;
      block_start = NR;
      print $0;
      next;
    }
    
    /^```$/ && in_block == 1 {
      in_block = 0;
      block_number++;
      system("dprint fmt " temp_block block_number " > " temp_block block_number ".fmt");
      system("cat " temp_block block_number ".fmt");
      print $0;
      next;
    }
    
    in_block == 1 {
      print $0 >> (temp_block block_number);
      next;
    }
    
    {
      print $0;
    }
    ' %s > %s && mv %s %s
  ]], vim.fn.shellescape(filepath), vim.fn.shellescape(temp_file), 
     vim.fn.shellescape(temp_file), vim.fn.shellescape(filepath))
  
  -- Execute the command
  local result = vim.fn.system(cmd)
  
  -- Check for errors
  if vim.v.shell_error ~= 0 then
    vim.notify("Error formatting TypeScript blocks: " .. result, vim.log.levels.ERROR)
  else
    -- Reload the file
    vim.cmd("edit!")
    vim.notify("TypeScript code blocks formatted", vim.log.levels.INFO)
  end
end, {})

If this is still problematic, let's step back and try a simpler approach. Let's create a command that directly uses the shell command you originally had, but with fixes for any issues:

vim.api.nvim_create_user_command('FormatTSMarkdown', function()
  -- Save the current file to ensure it's on disk
  vim.cmd("write")
  
  -- Get the current file path
  local filepath = vim.fn.expand("%:p")
  
  -- Create a shell command that's a simpler version of your original
  local cmd = string.format([[
    awk '/```typescript/{flag=1; print; next} 
         /```/{flag=0; print; next} 
         flag {code = code $0 "\n"}
         !flag {print}
         END {if (code != "") {
           printf "%s" | "dprint fmt --stdin .ts"
         }}' %s > %s.tmp && 
    mv %s.tmp %s
  ]], "%s", vim.fn.shellescape(filepath), 
     vim.fn.shellescape(filepath), 
     vim.fn.shellescape(filepath), 
     vim.fn.shellescape(filepath))
  
  -- Execute the command
  local result = vim.fn.system(cmd)
  
  -- Check for errors
  if vim.v.shell_error ~= 0 then
    vim.notify("Error formatting TypeScript blocks: " .. result, vim.log.levels.ERROR)
  else
    -- Reload the file
    vim.cmd("edit!")
    vim.notify("TypeScript code blocks formatted", vim.log.levels.INFO)
  end
end, {})

Actually, based on your original command, I see that we may be making things too complicated. Let's try to fix the original command while maintaining its general approach:

vim.api.nvim_create_user_command('FormatTSMarkdown', function()
  -- Save file
  vim.cmd("write")
  
  -- Get file path
  local filepath = vim.fn.expand("%:p")
  
  -- This is a fixed version of your original command
  local cmd = string.format([[
    awk '/```typescript/{flag=1; print; next} /```/{flag=0; print; next} flag{content=content$0"\n"} !flag{print}' %s | 
    (
      while IFS= read -r line; do
        if [[ "$line" == "```typescript" ]]; then
          echo "$line"
          read_ts=1
          ts_content=""
          continue
        fi
        
        if [[ "$line" == "```" && "$read_ts" == "1" ]]; then
          echo "$ts_content" | dprint fmt --stdin .ts
          echo "$line"
          read_ts=0
          continue
        fi
        
        if [[ "$read_ts" == "1" ]]; then
          ts_content="$ts_content$line\n"
        else
          echo "$line"
        fi
      done
    ) > %s.tmp && mv %s.tmp %s
  ]], 
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath))
  
  -- Execute command
  local result = vim.fn.system(cmd)
  
  -- Check for errors
  if vim.v.shell_error ~= 0 then
    vim.notify("Error formatting TypeScript blocks: " .. result, vim.log.levels.ERROR)
  else
    -- Reload file
    vim.cmd("edit!")
    vim.notify("TypeScript code blocks formatted", vim.log.levels.INFO)
  end
end, {})

The complexity of Shell script in Lua strings is getting unwieldy. Let's go back to your original command and make the minimal necessary changes to make it work:

vim.api.nvim_create_user_command('FormatTSMarkdown', function()
  -- Save current buffer
  vim.cmd("write")
  
  -- Get filename
  local filename = vim.fn.expand("%")
  
  -- Modified version of your original command
  local cmd = string.format([[
    awk '/```typescript/{flag=1; print; next} /```/{flag=0; print; next} flag{print | "dprint fmt --stdin .ts > /tmp/ts_formatted"} !flag{print}' %s > %s.tmp && cat /tmp/ts_formatted >> %s.tmp && mv %s.tmp %s
  ]], 
     filename, filename, filename, filename, filename)
  
  -- Execute the command
  local exit_code = os.execute(cmd)
  
  if exit_code ~= 0 then
    vim.notify("Error formatting TypeScript blocks", vim.log.levels.ERROR)
  else
    -- Reload the file
    vim.cmd("edit!")
    vim.notify("TypeScript code blocks formatted", vim.log.levels.INFO)
  end
end, {})

Let's try one more approach that's close to your original but should be more reliable:

vim.api.nvim_create_user_command('FormatTSMarkdown', function()
  -- Save the current buffer
  vim.cmd("write")
  
  -- Get the file path
  local filepath = vim.fn.expand("%:p")
  
  -- Create a temporary file for the typescript code
  local ts_temp = vim.fn.tempname()
  
  -- Execute the command to extract and format TypeScript blocks
  local cmd = string.format([[
    # Create an empty temp file
    > %s
    
    # Process the file
    while IFS= read -r line; do
      if [[ "$line" == "```typescript" ]]; then
        # Output the marker
        echo "$line"
        # Start collecting TypeScript code
        in_ts=1
        ts_content=""
        continue
      fi
      
      if [[ "$line" == "```" && "$in_ts" == "1" ]]; then
        # Format the collected TypeScript content
        echo "$ts_content" | dprint fmt --stdin .ts
        # Output the end marker
        echo "$line"
        # Reset the flag
        in_ts=0
        continue
      fi
      
      if [[ "$in_ts" == "1" ]]; then
        # Collect TypeScript code
        ts_content="$ts_content$line\n"
      else
        # Output non-TypeScript lines as-is
        echo "$line"
      fi
    done < %s > %s.tmp
    
    # Replace the original file
    mv %s.tmp %s
  ]],
     ts_temp,
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath))
  
  -- Execute the command
  local result = vim.fn.system(cmd)
  
  -- Check for errors
  if vim.v.shell_error ~= 0 then
    vim.notify("Error formatting TypeScript blocks: " .. result, vim.log.levels.ERROR)
  else
    -- Reload the file
    vim.cmd("edit!")
    vim.notify("TypeScript code blocks formatted", vim.log.levels.INFO)
  end
end, {})

Let's try one more extremely simple approach:

vim.api.nvim_create_user_command('FormatTSMarkdown', function()
  -- Save the file
  vim.cmd("write")
  
  -- Get the file path
  local filepath = vim.fn.expand("%:p")
  
  -- Simple command that extracts TypeScript blocks, formats them, and reinserts them
  local cmd = string.format([[
    # Extract TypeScript blocks and their line numbers
    grep -n "^```typescript$\|^```$" %s | awk '
      BEGIN { n = 0 }
      /```typescript/ { start[++n] = $1; }
      /```$/ { if (n > 0 && start[n] != "") { end[n] = $1; } }
      END {
        for (i = 1; i <= n; i++) {
          if (start[i] && end[i]) {
            print start[i] "," end[i];
          }
        }
      }
    ' | while read range; do
      # Extract block content without markers
      sed -n "$range"p %s | sed '1d;$d' > /tmp/ts_block.ts
      
      # Format the block
      dprint fmt /tmp/ts_block.ts
      
      # Replace the block in the file (this is the tricky part)
      start_line=$(echo $range | cut -d, -f1)
      end_line=$(echo $range | cut -d, -f2)
      
      # Create a temporary file with the content before the block
      sed -n "1,$((start_line-1))p" %s > /tmp/before.md
      
      # Create a temporary file with the content after the block
      sed -n "$((end_line+1)),\$p" %s > /tmp/after.md
      
      # Combine everything
      cat /tmp/before.md > /tmp/combined.md
      echo "```typescript" >> /tmp/combined.md
      cat /tmp/ts_block.ts >> /tmp/combined.md
      echo "```" >> /tmp/combined.md
      cat /tmp/after.md >> /tmp/combined.md
      
      # Replace the original file
      mv /tmp/combined.md %s
    done
  ]],
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath),
     vim.fn.shellescape(filepath))
  
  -- Execute the command
  local result = vim.fn.system(cmd)
  
  -- Check for errors
  if vim.v.shell_error ~= 0 then
    vim.notify("Error formatting TypeScript blocks: " .. result, vim.log.levels.ERROR)
  else
    -- Reload the file
    vim.cmd("edit!")
    vim.notify("TypeScript code blocks formatted", vim.log.levels.INFO)
  end
end, {})

Given the challenges, let's try a completely different strategy. Let's use a simple external script approach:

vim.api.nvim_create_user_command('FormatTSMarkdown', function()
  -- Save the file
  vim.cmd("write")
  
  -- Get the file path
  local filepath = vim.fn.expand("%:p")
  
  -- Create a temporary script file
  local script_file = vim.fn.tempname() .. ".sh"
  local file = io.open(script_file, "w")
  if not file then
    vim.notify("Failed to create temporary script file", vim.log.levels.ERROR)
    return
  end
  
  -- Write a bash script that handles the formatting
  file:write([[
#!/bin/bash
set -e

# Get the input file
INPUT_FILE="$1"
TEMP_FILE=$(mktemp)

# Process the file line by line
in_ts_block=0
ts_content=""
ts_block_start=0
line_num=0

while IFS= read -r line; do
  ((line_num++))
  
  if [[ "$line" == "```typescript" ]]; then
    in_ts_block=1
    ts_block_start=$line_num
    ts_content=""
    echo "$line" >> "$TEMP_FILE"
  elif [[ "$line" == "```" && "$in_ts_block" -eq 1 ]]; then
    in_ts_block=0
    
    # Format the TypeScript content
    formatted=$(echo -e "$ts_content" | dprint fmt --stdin .ts)
    if [ $? -eq 0 ]; then
      echo "$formatted" >> "$TEMP_FILE"
    else
      echo -e "$ts_content" >> "$TEMP_FILE"
    fi
    
    echo "$line" >> "$TEMP_FILE"
  elif [ "$in_ts_block" -eq 1 ]; then
    ts_content="${ts_content}${line}\n"
  else
    echo "$line" >> "$TEMP_FILE"
  fi
done < "$INPUT_FILE"

# Replace the original file
mv "$TEMP_FILE" "$INPUT_FILE"
]])
  file:close()
  
  -- Make the script executable
  os.execute("chmod +x " .. script_file)
  
  -- Run the script
  local cmd = string.format("%s %s", script_file, vim.fn.shellescape(filepath))
  local result = vim.fn.system(cmd)
  
  -- Check for errors
  if vim.v.shell_error ~= 0 then
    vim.notify("Error formatting TypeScript blocks: " .. result, vim.log.levels.ERROR)
  else
    -- Reload the file
    vim.cmd("edit!")
    vim.notify("TypeScript code blocks formatted", vim.log.levels.INFO)
  end
  
  -- Clean up
  os.remove(script_file)
end, {})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment