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, {})