Skip to content

Commit d2aba47

Browse files
committed
feat: optimize cURL generation, add PowerShell support, and update dependencies
1 parent 6295a23 commit d2aba47

7 files changed

Lines changed: 154 additions & 93 deletions

File tree

.idea/dataSources.local.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/workspace.xml

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

build.gradle.kts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,7 @@ dependencies {
8484
testBundledModules("intellij.libraries.ktor.client", "intellij.libraries.ktor.client.cio")
8585
testPlugins("Dart:$dartVersion")
8686
testFramework(TestFrameworkType.Platform)
87-
bundledModule("intellij.platform.jewel.foundation")
88-
bundledModule("intellij.platform.jewel.ui")
89-
bundledModule("intellij.platform.jewel.ideLafBridge")
90-
bundledModule("intellij.platform.jewel.markdown.core")
91-
bundledModule("intellij.platform.jewel.markdown.ideLafBridgeStyling")
92-
bundledModule("intellij.libraries.compose.foundation.desktop")
93-
bundledModule("intellij.libraries.skiko")
87+
composeUI()
9488
}
9589
testImplementation("junit:junit:4.13.2")
9690
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
kotlin.stdlib.default.dependency=false
2-
pluginVersion=6.9.0
2+
pluginVersion=6.9.1
33
dartVersion=500.0.0
44
sinceBuildVersion=252
55
kotlin.daemon.jvmargs=-Xmx5024m

src/main/kotlin/shop/itbug/flutterx/dsl/SettingPanel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fun settingPanel(
4949
cell(FlutterVersionCheckPanel(project = project))
5050
}
5151
row(PluginBundle.get("ignore_this_version_to_check")) {
52-
cell(FlutterVersionIgnoreList())
52+
scrollCell(FlutterVersionIgnoreList())
5353
}
5454
documentCommentRow(Links.CHECK_FLUTTER_VERSION_DOC_LINK)
5555
}

src/main/kotlin/shop/itbug/flutterx/model/IRequest.kt

Lines changed: 123 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.google.gson.GsonBuilder
55
import com.intellij.openapi.fileEditor.FileEditorManager
66
import com.intellij.openapi.project.Project
77
import com.intellij.openapi.ui.MessageType
8+
import com.intellij.openapi.util.SystemInfo
89
import com.intellij.testFramework.LightVirtualFile
910
import com.intellij.util.ui.JBUI
1011
import com.intellij.util.ui.UIUtil
@@ -220,112 +221,183 @@ fun formatSize(sizeInBytes: Long): String {
220221
return DecimalFormat("#,##0.#").format(sizeInBytes / 1024.0.pow(digitGroups.toDouble())) + " " + units[digitGroups]
221222
}
222223

223-
224224
/**
225-
* 将 IRequest 对象转换为与 Dart/Flutter DevTools "Copy as cURL" 功能风格完全一致的命令字符串。
226-
*
227-
* 特点:
228-
* - 使用 `--location` 自动处理重定向。
229-
* - 使用 `--compressed` 处理压缩。
230-
* - 精确的参数顺序 (`--request METHOD 'URL' ...`)。
231-
* - 自动过滤掉由 cURL 管理的冗余头信息 (Host, Content-Length, Accept-Encoding)。
232-
* - 格式化为易于阅读和粘贴的多行命令。
233-
* - 跨平台支持:自动适配 Windows (CMD/PowerShell) 和 Unix-like 系统的语法差异。
234-
*
235-
* @param gsonInstance 一个可选的 Gson 实例,用于序列化 JSON 请求体。如果为 null,将创建一个新的实例。
236-
* @return 格式化后的 cURL 命令字符串。
225+
* 优化后的 cURL 生成函数
237226
*/
238227
fun IRequest.toCurlStringAsDartDevTools(gsonInstance: Gson? = null): String {
239-
// 检测操作系统
240-
val isWindows = System.getProperty("os.name").lowercase().contains("win")
228+
val isWindows = SystemInfo.isWindows
241229

242-
// 根据操作系统选择引号和转义方式
230+
// 1. 确定 Shell 环境下的引号和换行符
231+
// 即使在 Windows 上,我们也倾向于生成 curl.exe 以规避 PowerShell 别名问题
232+
val cmdBase = if (isWindows) "curl.exe" else "curl"
243233
val quote = if (isWindows) "\"" else "'"
244234
val lineContinuation = if (isWindows) " `" else " \\"
245235

246-
// 转义函数:根据操作系统选择不同的转义策略
236+
// 2. 增强型转义函数
247237
fun escapeForShell(text: String): String {
248238
return if (isWindows) {
249-
// Windows: 转义双引号和反斜杠
250-
text.replace("\\", "\\\\").replace("\"", "\\\"")
239+
// Windows 双引号内:转义双引号
240+
// 特殊情况:PowerShell 里的 $ 需要转义,但 curl.exe 本身在 CMD 运行不需要。
241+
// 综合考虑,转义双引号是最基础的。
242+
text.replace("\"", "\\\"")
251243
} else {
252-
// Unix: 转义单引号
244+
// Unix 单引号内:将 ' 替换为 '\'' (结束当前单引号,转义单引号,开始新单引号)
253245
text.replace("'", "'\\''")
254246
}
255247
}
256248

257-
// 创建一个列表来收集 cURL 命令的所有部分
258249
val commandParts = mutableListOf<String>()
259250

260-
// 1. HTTP 方法 (--request)
251+
// 3. HTTP 方法
261252
httpMethod?.let {
262253
commandParts.add("--request ${it.uppercase()}")
263254
}
264255

265-
// 2. 构建并添加最终的 URL (包含查询参数)
256+
// 4. 处理 URL 和多值查询参数
266257
val finalUrl = buildString {
267258
append(requestUrl)
268259
if (queryParams.isNotEmpty()) {
269-
val queryString = queryParams.map { (key, value) ->
260+
val queryString = queryParams.entries.flatMap { (key, value) ->
270261
val encodedKey = URLEncoder.encode(key, "UTF-8")
271-
val encodedValue = URLEncoder.encode(value?.toString() ?: "", "UTF-8")
272-
"$encodedKey=$encodedValue"
262+
// 处理 List 类型的多值参数,例如 id=1&id=2
263+
val values = when (value) {
264+
is Iterable<*> -> value
265+
is Array<*> -> value.toList()
266+
else -> listOf(value)
267+
}
268+
values.map { v ->
269+
val encodedValue = URLEncoder.encode(v?.toString() ?: "", "UTF-8")
270+
"$encodedKey=$encodedValue"
271+
}
273272
}.joinToString("&")
274273

275-
if (requestUrl.contains("?")) {
276-
append("&$queryString")
277-
} else {
278-
append("?$queryString")
274+
if (queryString.isNotEmpty()) {
275+
append(if (requestUrl.contains("?")) "&" else "?")
276+
append(queryString)
279277
}
280278
}
281279
}
282280
commandParts.add("$quote${escapeForShell(finalUrl)}$quote")
283281

284-
// 3. 添加请求头 (--header)
285-
val headersToFilter = setOf("host", "accept-encoding", "content-length")
282+
// 5. 添加通用的配置参数
283+
commandParts.add("--location")
284+
commandParts.add("--compressed")
285+
286+
// 6. 添加请求头
287+
val headersToFilter = setOf("host", "content-length") // 允许 accept-encoding,因为 curl 可以处理
286288
httpRequestHeaders
287289
.filterKeys { key -> !headersToFilter.contains(key.lowercase()) }
288290
.forEach { (key, value) ->
289291
val escapedValue = escapeForShell(value.toString())
290292
commandParts.add("--header $quote$key: $escapedValue$quote")
291293
}
292294

293-
// 4. 智能处理请求体 (--data-raw, --data)
295+
// 7. 处理请求体
294296
httpRequestBody?.let { body ->
295297
val contentType = httpRequestHeaders.entries
296298
.firstOrNull { it.key.equals("Content-Type", ignoreCase = true) }
297299
?.value?.toString()?.lowercase() ?: ""
298300

299-
val bodyPart = when {
301+
when {
300302
contentType.contains("application/json") -> {
301303
val jsonBody = when (body) {
302304
is String -> body
303-
else -> (gsonInstance ?: Gson()).toJson(body)
305+
else -> (gsonInstance ?: GsonBuilder().disableHtmlEscaping().create()).toJson(body)
304306
}
305-
val escapedBody = escapeForShell(jsonBody)
306-
"--data-raw $quote$escapedBody$quote"
307+
commandParts.add("--data-raw ${quote}${escapeForShell(jsonBody)}${quote}")
308+
}
309+
contentType.contains("application/x-www-form-urlencoded") -> {
310+
val formData = if (body is Map<*, *>) {
311+
body.map { (k, v) ->
312+
"${URLEncoder.encode(k.toString(), "UTF-8")}=${URLEncoder.encode(v?.toString() ?: "", "UTF-8")}"
313+
}.joinToString("&")
314+
} else body.toString()
315+
commandParts.add("--data ${quote}${escapeForShell(formData)}${quote}")
316+
}
317+
else -> {
318+
commandParts.add("--data-binary ${quote}${escapeForShell(body.toString())}${quote}")
307319
}
320+
}
321+
}
322+
323+
// 8. 组装命令
324+
return "$cmdBase " + commandParts.joinToString(separator = "$lineContinuation\n ")
325+
}
326+
327+
308328

309-
contentType.contains("application/x-www-form-urlencoded") && body is Map<*, *> -> {
310-
val formData = body.map { (key, value) ->
311-
val encodedKey = URLEncoder.encode(key.toString(), "UTF-8")
329+
/**
330+
* 将 IRequest 转换为 PowerShell 的 Invoke-RestMethod 命令字符串
331+
*/
332+
fun IRequest.toPowerShellString(gsonInstance: Gson? = null): String {
333+
val gson = gsonInstance ?: GsonBuilder().setPrettyPrinting().create()
334+
val indent = " " // 用于美化输出的缩进
335+
val lineContinuation = " `" // PowerShell 的换行符
336+
337+
return buildString {
338+
append("Invoke-RestMethod")
339+
append(lineContinuation)
340+
append("\n")
341+
342+
// 1. Method
343+
val method = (httpMethod ?: "GET").uppercase()
344+
append("$indent-Method $method")
345+
append(lineContinuation)
346+
append("\n")
347+
348+
// 2. Uri (处理查询参数)
349+
val finalUrl = buildString {
350+
append(requestUrl)
351+
if (queryParams.isNotEmpty()) {
352+
val queryString = queryParams.map { (key, value) ->
353+
val encodedKey = URLEncoder.encode(key, "UTF-8")
312354
val encodedValue = URLEncoder.encode(value?.toString() ?: "", "UTF-8")
313355
"$encodedKey=$encodedValue"
314356
}.joinToString("&")
315-
"--data $quote$formData$quote"
357+
358+
if (requestUrl.contains("?")) {
359+
append("&$queryString")
360+
} else {
361+
append("?$queryString")
362+
}
316363
}
364+
}
365+
// PowerShell 中 URL 如果包含 $ 符号需要转义,这里简单处理
366+
val escapedUrl = finalUrl.replace("$", "`$")
367+
append("$indent-Uri \"$escapedUrl\"")
368+
369+
// 3. Headers
370+
if (httpRequestHeaders.isNotEmpty()) {
371+
append(lineContinuation)
372+
append("\n")
373+
append("$indent-Headers @{")
374+
append("\n")
375+
httpRequestHeaders.forEach { (key, value) ->
376+
// 转义头信息中的双引号
377+
val escapedValue = value.toString().replace("\"", "`\"")
378+
append("$indent$indent\"$key\" = \"$escapedValue\"\n")
379+
}
380+
append("$indent}")
381+
}
317382

318-
else -> {
319-
val escapedBody = escapeForShell(body.toString())
320-
"--data $quote$escapedBody$quote"
383+
// 4. Body (如果是 POST/PUT/PATCH 且有请求体)
384+
val methodsWithBody = listOf("POST", "PUT", "PATCH", "DELETE")
385+
if (method in methodsWithBody && httpRequestBody != null) {
386+
append(lineContinuation)
387+
append("\n")
388+
389+
val bodyString = when (val body = httpRequestBody) {
390+
is String -> body
391+
else -> gson.toJson(body)
321392
}
393+
394+
// 在 PowerShell 双引号字符串中,内部双引号建议使用 "" 来表示
395+
val escapedBody = bodyString.replace("\"", "\"\"")
396+
397+
append("$indent-ContentType \"application/json; charset=utf-8\"")
398+
append(lineContinuation)
399+
append("\n")
400+
append("$indent-Body \"$escapedBody\"")
322401
}
323-
commandParts.add(bodyPart)
324402
}
325-
326-
// 5. 组合所有部分
327-
// 使用适合当前操作系统的续行符
328-
return "curl --location --compressed$lineContinuation\n " +
329-
commandParts.joinToString(separator = "$lineContinuation\n ")
330403
}
331-

0 commit comments

Comments
 (0)