Skip to content

Commit 7f97657

Browse files
committed
perf(feat): improved smart formatting
1 parent f522bc9 commit 7f97657

File tree

3 files changed

+77
-42
lines changed

3 files changed

+77
-42
lines changed

README.md

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# sql-formatter.nvim
22

3-
A lightweight, high-performance SQL formatter plugin for Neovim that leverages `sqlparse` for optimal formatting results with a Lua fallback for basic formatting.
3+
A lightweight, high-performance SQL formatter plugin for Neovim that leverages `sql-formatter` (Node.js) or `sqlparse` (Python) for optimal formatting results, with a Lua fallback for basic formatting.
44

55
## Images
66
<p float="left">
@@ -10,24 +10,32 @@ A lightweight, high-performance SQL formatter plugin for Neovim that leverages `
1010

1111
## Features
1212

13-
- 🚀 **High Performance**: Uses external `sqlparse` formatter for speed
13+
- 🚀 **High Performance**: Uses external `sql-formatter` (Node.js) or `sqlparse` (Python) for speed
1414
- 🔧 **Configurable**: Extensive customization options
1515
- 📝 **Format on Save**: Automatic formatting when saving files
1616
- ⌨️ **Key Bindings**: Convenient shortcuts for formatting
1717
- 🎯 **Multi-dialect**: Support for PostgreSQL, MySQL, SQLite, and more
18-
- 🔄 **Fallback**: Pure Lua formatter when external tools unavailable
18+
- 🔄 **Fallback**: Improved Lua formatter when external tools unavailable
1919
- 📋 **Range Formatting**: Format selected text only
2020

2121
## Requirements
2222

2323
- Neovim >= 0.8.0
24-
- Optional: `sqlparse` for optimal performance (`pip install sqlparse`)
24+
- Optional: `sql-formatter` (Node.js) **or** `sqlparse` (Python) for optimal performance
2525

2626
## Installation
2727

2828
### Prerequisites
2929

30-
For optimal performance, install `sqlparse`:
30+
For optimal performance, install either `sql-formatter` (recommended) or `sqlparse`:
31+
32+
#### Option 1: sql-formatter (Node.js, recommended)
33+
34+
```bash
35+
npm install -g sql-formatter
36+
```
37+
38+
#### Option 2: sqlparse (Python)
3139

3240
```bash
3341
pip install sqlparse
@@ -110,17 +118,15 @@ require("sql-formatter").setup({
110118
toggle_format_on_save = "<leader>st",
111119
},
112120

113-
-- External formatter (sqlparse)
121+
-- External formatter (choose one)
114122
external_formatter = {
115123
enabled = true,
116-
command = "sqlformat",
117-
args = {
118-
"--reindent",
119-
"--keywords", "upper",
120-
"--identifiers", "lower",
121-
"--strip-comments",
122-
"-"
123-
}
124+
-- Use sql-formatter (Node.js):
125+
command = "sql-formatter",
126+
args = {},
127+
-- Or use sqlparse (Python):
128+
-- command = "sqlformat",
129+
-- args = { "--reindent", "--keywords", "upper", "--identifiers", "lower", "--strip-comments", "-" }
124130
},
125131

126132
-- Notifications
@@ -172,8 +178,8 @@ ORDER BY u.created_at DESC;
172178

173179
This plugin prioritizes performance by:
174180

175-
1. **External Formatter**: Uses `sqlparse` (Python) for heavy lifting
176-
2. **Lua Fallback**: Lightweight Lua formatter when external tools unavailable
181+
1. **External Formatter**: Uses `sql-formatter` (Node.js, recommended) or `sqlparse` (Python) for heavy lifting
182+
2. **Lua Fallback**: Improved Lua formatter when external tools unavailable
177183
3. **Lazy Loading**: Only loads for SQL file types
178184
4. **Minimal Dependencies**: Pure Lua implementation with optional external tools
179185

lua/sql-formatter/formatter.lua

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function M.setup(config)
1212
M.config = config
1313
end
1414

15-
-- Check if external formatter (sqlparse) is available
15+
-- Check if external formatter (sqlparse or sql-formatter) is available
1616
function M.check_external_formatter()
1717
if not M.config.external_formatter.enabled then
1818
return false
@@ -23,23 +23,36 @@ function M.check_external_formatter()
2323
handle:close()
2424
M.external_available = result ~= ""
2525
if not M.external_available then
26+
local install_msg = ""
27+
if cmd == "sql-formatter" then
28+
install_msg = "Install with: npm install -g sql-formatter"
29+
elseif cmd == "sqlparse" then
30+
install_msg = "Install with: pip install sqlparse"
31+
end
2632
vim.notify(
27-
"External SQL formatter '" .. cmd .. "' not found. Install with: pip install sqlparse",
33+
"External SQL formatter '" .. cmd .. "' not found. " .. install_msg,
2834
vim.log.levels.WARN,
2935
{ title = "SQL Formatter" }
3036
)
3137
end
3238
return M.external_available
3339
end
3440

35-
-- Format SQL using external formatter (sqlparse)
41+
-- Format SQL using external formatter (sqlparse or sql-formatter)
3642
function M.format_with_external(text)
3743
if not M.external_available then
3844
return nil, "External formatter not available"
3945
end
4046
local cmd = M.config.external_formatter.command
4147
local args = table.concat(M.config.external_formatter.args, " ")
42-
local full_cmd = string.format("echo %s | %s %s", vim.fn.shellescape(text), cmd, args)
48+
local full_cmd
49+
if cmd == "sql-formatter" then
50+
-- sql-formatter expects input from stdin
51+
full_cmd = string.format("echo %s | %s %s", vim.fn.shellescape(text), cmd, args)
52+
else
53+
-- default: sqlparse or other
54+
full_cmd = string.format("echo %s | %s %s", vim.fn.shellescape(text), cmd, args)
55+
end
4356
local handle = io.popen(full_cmd)
4457
if not handle then
4558
return nil, "Failed to execute formatter command"
@@ -52,31 +65,47 @@ function M.format_with_external(text)
5265
return result:gsub("%s+$", ""), nil -- Remove trailing whitespace
5366
end
5467

55-
-- Fallback Lua-based formatter (basic)
68+
-- Improved Lua-based formatter (smarter, more beautiful)
5669
function M.format_with_lua(text)
5770
local lines = vim.split(text, "\n")
5871
local formatted_lines = {}
5972
local indent_level = 0
60-
local indent = M.config.indent
61-
for _, line in ipairs(lines) do
73+
local indent = M.config.indent or " "
74+
local clause_keywords = {
75+
"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "HAVING", "LIMIT", "OFFSET", "JOIN", "LEFT JOIN", "RIGHT JOIN", "INNER JOIN", "OUTER JOIN", "ON", "UNION", "VALUES", "SET", "INSERT", "UPDATE", "DELETE"
76+
}
77+
local function is_clause(line)
78+
for _, kw in ipairs(clause_keywords) do
79+
if line:upper():match("^" .. kw) then
80+
return true
81+
end
82+
end
83+
return false
84+
end
85+
local function add_linebreaks(sql)
86+
-- Add line breaks before major clauses
87+
for _, kw in ipairs(clause_keywords) do
88+
sql = sql:gsub("%s+" .. kw, "\n" .. kw)
89+
end
90+
return sql
91+
end
92+
-- Preprocess: add line breaks for major clauses
93+
local preprocessed = add_linebreaks(text)
94+
local split_lines = vim.split(preprocessed, "\n")
95+
for i, line in ipairs(split_lines) do
6296
local trimmed = line:match("^%s*(.-)%s*$")
6397
if trimmed ~= "" and not utils.is_comment_line(trimmed) then
64-
-- Handle keywords that decrease indent
65-
if trimmed:upper():match("^(END|ELSE|ELSIF|WHEN)%s") then
66-
indent_level = math.max(0, indent_level - 1)
98+
-- Indent columns in SELECT
99+
if i > 1 and split_lines[i-1]:upper():match("^SELECT") and not is_clause(trimmed) then
100+
indent_level = 1
101+
elseif is_clause(trimmed) then
102+
indent_level = 0
67103
end
68-
-- Apply current indentation
69104
local formatted_line = string.rep(indent, indent_level) .. trimmed
70-
-- Format keywords to uppercase if configured
71105
if M.config.uppercase then
72106
formatted_line = M.format_keywords(formatted_line)
73107
end
74108
table.insert(formatted_lines, formatted_line)
75-
-- Handle keywords that increase indent
76-
if trimmed:upper():match("^(SELECT|FROM|WHERE|JOIN|INNER|LEFT|RIGHT|FULL|CASE|BEGIN|IF|LOOP|WHILE)%s") or
77-
trimmed:upper():match("(THEN|ELSE|DO)%s*$") then
78-
indent_level = indent_level + 1
79-
end
80109
else
81110
table.insert(formatted_lines, trimmed)
82111
end

lua/sql-formatter/init.lua

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ local default_config = {
3030
level = "info",
3131
timeout = 2000,
3232
},
33-
-- External formatter command (using sqlparse)
33+
-- External formatter command (using sql-formatter or sqlparse)
34+
-- To use sql-formatter (Node.js):
35+
-- command = "sql-formatter",
36+
-- args = { "--config", "/path/to/.sql-formatter.json" } -- optional
37+
-- To use sqlparse (Python):
38+
-- command = "sqlformat",
39+
-- args = { "--reindent", "--keywords", "upper", "--identifiers", "lower", "--strip-comments", "-" }
3440
external_formatter = {
3541
enabled = true,
36-
command = "sqlformat",
37-
args = {
38-
"--reindent",
39-
"--keywords", "upper",
40-
"--identifiers", "lower",
41-
"--strip-comments",
42-
"-"
43-
}
42+
command = "sql-formatter", -- or "sqlformat"
43+
args = {}, -- see above for examples
4444
}
4545
}
4646

0 commit comments

Comments
 (0)