Skip to content

Commit e9b859e

Browse files
committed
support format with lsp options, add external format document
1 parent 6646630 commit e9b859e

File tree

8 files changed

+321
-12
lines changed

8 files changed

+321
-12
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ tokio-util = "0.7.15"
3030
walkdir = "2.5.0"
3131
serde_yml = "0.0.12"
3232
dirs = "6"
33-
emmylua_codestyle = "0.4.1"
33+
emmylua_codestyle = "0.5.0"
3434
wax = "0.6.0"
3535
percent-encoding = "2.3"
3636
flagset = "0.4.6"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ EmmyLua Analyzer Rust implements the standard LSP protocol, making it compatible
254254
- [📖 **Features Guide**](./docs/features/features_EN.md) - Comprehensive feature documentation
255255
- [⚙️ **Configuration**](./docs/config/emmyrc_json_EN.md) - Advanced configuration options
256256
- [🎨 **Code Style**](https://github.com/CppCXY/EmmyLuaCodeStyle/blob/master/README_EN.md) - Formatting and style guidelines
257-
257+
- [🛠️ **External Formatter Integration**](./docs/external_format/external_formatter_options_EN.md) - Using external formatters
258258
---
259259

260260
## 🛠️ Usage & Examples

crates/emmylua_ls/src/handlers/document_formatting/external_format.rs

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use emmylua_code_analysis::EmmyrcExternalTool;
1+
use emmylua_code_analysis::{EmmyrcExternalTool, FormattingOptions};
22
use std::process::Stdio;
33
use std::time::Duration;
44
use tokio::io::AsyncWriteExt;
@@ -9,6 +9,7 @@ pub async fn external_tool_format(
99
emmyrc_external_tool: &EmmyrcExternalTool,
1010
text: &str,
1111
file_path: &str,
12+
options: FormattingOptions,
1213
) -> Option<String> {
1314
let exe_path = &emmyrc_external_tool.program;
1415
let args = &emmyrc_external_tool.args;
@@ -17,8 +18,9 @@ pub async fn external_tool_format(
1718
let mut cmd = Command::new(exe_path);
1819

1920
for arg in args {
20-
let processed_arg = arg.replace("${file}", file_path);
21-
cmd.arg(processed_arg);
21+
if let Some(processed_arg) = parse_macro_arg(arg, file_path, &options) {
22+
cmd.arg(processed_arg);
23+
}
2224
}
2325

2426
cmd.stdin(Stdio::piped())
@@ -79,3 +81,97 @@ pub async fn external_tool_format(
7981
}
8082
}
8183
}
84+
85+
fn parse_macro_arg(arg: &str, file_path: &str, options: &FormattingOptions) -> Option<String> {
86+
let mut result = String::new();
87+
let mut chars = arg.chars().peekable();
88+
89+
while let Some(ch) = chars.next() {
90+
if ch == '$' && chars.peek() == Some(&'{') {
91+
chars.next();
92+
93+
// collect ${} content
94+
let mut content = String::new();
95+
let mut brace_count = 1;
96+
97+
while let Some(inner_ch) = chars.next() {
98+
if inner_ch == '{' {
99+
brace_count += 1;
100+
if brace_count > 1 {
101+
content.push(inner_ch);
102+
}
103+
} else if inner_ch == '}' {
104+
brace_count -= 1;
105+
if brace_count == 0 {
106+
break;
107+
}
108+
content.push(inner_ch);
109+
} else {
110+
content.push(inner_ch);
111+
}
112+
}
113+
114+
// parse content
115+
let replacement = if content.contains('?') {
116+
// handle ${key:value} format
117+
let parts: Vec<&str> = content.splitn(2, '?').collect();
118+
if parts.len() == 2 {
119+
let key = parts[0].trim();
120+
let value = parts[1].trim();
121+
122+
let (true_value, fail_value) = if value.contains(':') {
123+
let value_parts = value.splitn(2, ':').collect::<Vec<&str>>();
124+
let true_value = value_parts[0].trim();
125+
let fail_value = value_parts.get(1).map_or("", |s| s.trim());
126+
(true_value, fail_value)
127+
} else {
128+
(value, "")
129+
};
130+
131+
match key {
132+
"use_tabs" => {
133+
if options.use_tabs {
134+
true_value.to_string()
135+
} else {
136+
fail_value.to_string()
137+
}
138+
}
139+
"insert_final_newline" => {
140+
if options.insert_final_newline {
141+
true_value.to_string()
142+
} else {
143+
fail_value.to_string()
144+
}
145+
}
146+
"non_standard_symbol" => {
147+
if options.non_standard_symbol {
148+
true_value.to_string()
149+
} else {
150+
fail_value.to_string()
151+
}
152+
}
153+
_ => true_value.to_string(), // if not a predefined key, return value
154+
}
155+
} else {
156+
content.clone()
157+
}
158+
} else {
159+
// handle ${variable} format
160+
match content.trim() {
161+
"file" => file_path.to_string(),
162+
"indent_size" => options.indent_size.to_string(),
163+
_ => "".to_string(),
164+
}
165+
};
166+
167+
result.push_str(&replacement);
168+
} else {
169+
result.push(ch);
170+
}
171+
}
172+
173+
if result.is_empty() {
174+
return None; // if no content was processed, return None
175+
}
176+
Some(result)
177+
}

crates/emmylua_ls/src/handlers/document_formatting/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod external_format;
22

3-
use emmylua_code_analysis::reformat_code;
3+
use emmylua_code_analysis::{FormattingOptions, reformat_code};
44
use lsp_types::{
55
ClientCapabilities, DocumentFormattingParams, OneOf, ServerCapabilities, TextEdit,
66
};
@@ -43,11 +43,17 @@ pub async fn on_formatting_handler(
4343
let text = document.get_text();
4444
let file_path = document.get_file_path();
4545
let normalized_path = file_path.to_string_lossy().to_string().replace("\\", "/");
46+
let formatting_options = FormattingOptions {
47+
indent_size: params.options.tab_size,
48+
use_tabs: !params.options.insert_spaces,
49+
insert_final_newline: params.options.insert_final_newline.unwrap_or(true),
50+
non_standard_symbol: !emmyrc.runtime.nonstandard_symbol.is_empty(),
51+
};
4652

4753
let mut formatted_text = if let Some(external_config) = &emmyrc.format.external_tool {
48-
external_tool_format(&external_config, text, &normalized_path).await?
54+
external_tool_format(&external_config, text, &normalized_path, formatting_options).await?
4955
} else {
50-
reformat_code(text, &normalized_path)
56+
reformat_code(text, &normalized_path, formatting_options)
5157
};
5258

5359
if client_id.is_intellij() || client_id.is_other() {

crates/emmylua_ls/src/handlers/document_range_formatting/mod.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use emmylua_code_analysis::range_format_code;
1+
use emmylua_code_analysis::{FormattingOptions, range_format_code};
22
use lsp_types::{
33
ClientCapabilities, DocumentRangeFormattingParams, OneOf, Position, Range, ServerCapabilities,
44
TextEdit,
@@ -29,7 +29,7 @@ pub async fn on_range_formatting_handler(
2929
if syntax_tree.has_syntax_errors() {
3030
return None;
3131
}
32-
32+
let emmyrc = analysis.get_emmyrc();
3333
let document = analysis
3434
.compilation
3535
.get_db()
@@ -38,13 +38,20 @@ pub async fn on_range_formatting_handler(
3838
let text = document.get_text();
3939
let file_path = document.get_file_path();
4040
let normalized_path = file_path.to_string_lossy().to_string().replace("\\", "/");
41+
let formatting_options = FormattingOptions {
42+
indent_size: params.options.tab_size,
43+
use_tabs: !params.options.insert_spaces,
44+
insert_final_newline: params.options.insert_final_newline.unwrap_or(true),
45+
non_standard_symbol: !emmyrc.runtime.nonstandard_symbol.is_empty(),
46+
};
4147
let formatted_result = range_format_code(
4248
text,
4349
&normalized_path,
4450
request_range.start.line as i32,
4551
0,
4652
request_range.end.line as i32 + 1,
4753
0,
54+
formatting_options,
4855
)?;
4956

5057
let mut formatted_text = formatted_result.text;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# 外部格式化工具选项
2+
3+
emmyLua_ls支持使用外部格式化工具来格式化 Lua 代码。通过配置 `.emmyrc.json` 文件,你可以集成任何支持命令行的代码格式化工具。
4+
5+
## 配置格式
6+
7+
`.emmyrc.json` 文件中,你可以配置外部格式化工具:
8+
9+
```json
10+
{
11+
"format" : {
12+
"externalTool": {
13+
"program": "stylua",
14+
"args": [
15+
"-",
16+
"--stdin-filepath",
17+
"${file}",
18+
],
19+
"timeout": 5000
20+
}
21+
}
22+
}
23+
```
24+
25+
## 配置项说明
26+
27+
- **program**: 外部格式化工具的可执行文件路径
28+
- **args**: 传递给格式化工具的参数列表
29+
- **timeout**: 格式化操作的超时时间(毫秒),默认值为 5000ms
30+
31+
## 变量替换
32+
33+
`args` 参数中,你可以使用以下变量,它们会在运行时被实际值替换:
34+
35+
### 简单变量
36+
37+
| 变量 | 描述 | 示例值 |
38+
|------|------|--------|
39+
| `${file}` | 当前文件的完整路径 | `/path/to/script.lua` |
40+
| `${indent_size}` | 缩进大小(空格数) | `4` |
41+
42+
### 条件变量
43+
44+
条件变量使用 `${variable?true_value:false_value}` 的格式,根据条件返回不同的值:
45+
46+
| 变量 | 描述 | true 时返回 | false 时返回 |
47+
|------|------|-------------|--------------|
48+
| `${use_tabs?--use-tabs:--use-spaces}` | 是否使用制表符缩进 | `--use-tabs` | `--use-spaces` |
49+
| `${insert_final_newline?--final-newline:}` | 是否在文件末尾插入换行符 | `--final-newline` | 空字符串 |
50+
| `${non_standard_symbol?--allow-non-standard}` | 是否允许非标准符号 | `--allow-non-standard` | 空字符串 |
51+
52+
## 变量语法说明
53+
54+
### 基本语法
55+
- `${variable}` - 简单变量替换
56+
- `${variable?value}` - 条件变量,当条件为真时返回 value,否则返回空字符串
57+
- `${variable?true_value:false_value}` - 条件变量,根据条件返回不同的值
58+
59+
### 特殊处理
60+
- 如果条件变量的结果为空字符串,该参数将不会传递给外部工具
61+
- 变量名区分大小写
62+
- 未知的变量将保持原样不被替换
63+
64+
## 配置示例
65+
66+
### 使用 Stylua 格式化器
67+
68+
```json
69+
{
70+
"format" : {
71+
"externalTool": {
72+
"program": "stylua",
73+
"args": [
74+
"-",
75+
"--stdin-filepath",
76+
"${file}",
77+
"--indent-width=${indent_size}",
78+
"--indent-type",
79+
"${use_tabs?Tabs:Spaces}"
80+
]
81+
}
82+
}
83+
}
84+
```
85+
86+
## 工作流程
87+
88+
1. 当用户触发代码格式化时,EmmyLua 分析器会读取配置的外部工具设置
89+
2. 解析 `args` 中的变量,将它们替换为实际值
90+
3. 启动外部格式化工具,并将当前代码通过 stdin 传递给它
91+
4. 等待外部工具完成处理,读取格式化后的代码
92+
5. 如果格式化成功,将结果应用到编辑器中
93+
94+
## 错误处理
95+
96+
- 如果外部工具不存在或无法执行,会记录错误日志
97+
- 如果格式化过程超时,会终止进程并记录超时错误
98+
- 如果外部工具返回非零退出码,会记录错误信息
99+
- 如果输出不是有效的 UTF-8 文本,会记录编码错误

0 commit comments

Comments
 (0)