@@ -5,6 +5,7 @@ import com.google.gson.GsonBuilder
55import com.intellij.openapi.fileEditor.FileEditorManager
66import com.intellij.openapi.project.Project
77import com.intellij.openapi.ui.MessageType
8+ import com.intellij.openapi.util.SystemInfo
89import com.intellij.testFramework.LightVirtualFile
910import com.intellij.util.ui.JBUI
1011import 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 */
238227fun 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