🚧 Improve HTTP language parser
This commit is contained in:
4
frontend/bindings/net/http/index.ts
Normal file
4
frontend/bindings/net/http/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export * from "./models.js";
|
||||||
14
frontend/bindings/net/http/models.ts
Normal file
14
frontend/bindings/net/http/models.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Header represents the key-value pairs in an HTTP header.
|
||||||
|
*
|
||||||
|
* The keys should be in canonical form, as returned by
|
||||||
|
* [CanonicalHeaderKey].
|
||||||
|
*/
|
||||||
|
export type Header = { [_: string]: string[] };
|
||||||
4
frontend/bindings/time/index.ts
Normal file
4
frontend/bindings/time/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export * from "./models.js";
|
||||||
51
frontend/bindings/time/models.ts
Normal file
51
frontend/bindings/time/models.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Time represents an instant in time with nanosecond precision.
|
||||||
|
*
|
||||||
|
* Programs using times should typically store and pass them as values,
|
||||||
|
* not pointers. That is, time variables and struct fields should be of
|
||||||
|
* type [time.Time], not *time.Time.
|
||||||
|
*
|
||||||
|
* A Time value can be used by multiple goroutines simultaneously except
|
||||||
|
* that the methods [Time.GobDecode], [Time.UnmarshalBinary], [Time.UnmarshalJSON] and
|
||||||
|
* [Time.UnmarshalText] are not concurrency-safe.
|
||||||
|
*
|
||||||
|
* Time instants can be compared using the [Time.Before], [Time.After], and [Time.Equal] methods.
|
||||||
|
* The [Time.Sub] method subtracts two instants, producing a [Duration].
|
||||||
|
* The [Time.Add] method adds a Time and a Duration, producing a Time.
|
||||||
|
*
|
||||||
|
* The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.
|
||||||
|
* As this time is unlikely to come up in practice, the [Time.IsZero] method gives
|
||||||
|
* a simple way of detecting a time that has not been initialized explicitly.
|
||||||
|
*
|
||||||
|
* Each time has an associated [Location]. The methods [Time.Local], [Time.UTC], and Time.In return a
|
||||||
|
* Time with a specific Location. Changing the Location of a Time value with
|
||||||
|
* these methods does not change the actual instant it represents, only the time
|
||||||
|
* zone in which to interpret it.
|
||||||
|
*
|
||||||
|
* Representations of a Time value saved by the [Time.GobEncode], [Time.MarshalBinary], [Time.AppendBinary],
|
||||||
|
* [Time.MarshalJSON], [Time.MarshalText] and [Time.AppendText] methods store the [Time.Location]'s offset,
|
||||||
|
* but not the location name. They therefore lose information about Daylight Saving Time.
|
||||||
|
*
|
||||||
|
* In addition to the required “wall clock” reading, a Time may contain an optional
|
||||||
|
* reading of the current process's monotonic clock, to provide additional precision
|
||||||
|
* for comparison or subtraction.
|
||||||
|
* See the “Monotonic Clocks” section in the package documentation for details.
|
||||||
|
*
|
||||||
|
* Note that the Go == operator compares not just the time instant but also the
|
||||||
|
* Location and the monotonic clock reading. Therefore, Time values should not
|
||||||
|
* be used as map or database keys without first guaranteeing that the
|
||||||
|
* identical Location has been set for all values, which can be achieved
|
||||||
|
* through use of the UTC or Local method, and that the monotonic clock reading
|
||||||
|
* has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u)
|
||||||
|
* to t == u, since t.Equal uses the most accurate comparison available and
|
||||||
|
* correctly handles the case when only one of its arguments has a monotonic
|
||||||
|
* clock reading.
|
||||||
|
*/
|
||||||
|
export type Time = any;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpClientService HTTP客户端服务
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as $models from "./models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecuteRequest 执行HTTP请求
|
||||||
|
*/
|
||||||
|
export function ExecuteRequest(request: $models.HttpRequest | null): Promise<$models.HttpResponse | null> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3143343977, request) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType1($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = $models.HttpResponse.createFrom;
|
||||||
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
@@ -8,6 +8,7 @@ import * as DialogService from "./dialogservice.js";
|
|||||||
import * as DocumentService from "./documentservice.js";
|
import * as DocumentService from "./documentservice.js";
|
||||||
import * as ExtensionService from "./extensionservice.js";
|
import * as ExtensionService from "./extensionservice.js";
|
||||||
import * as HotkeyService from "./hotkeyservice.js";
|
import * as HotkeyService from "./hotkeyservice.js";
|
||||||
|
import * as HttpClientService from "./httpclientservice.js";
|
||||||
import * as KeyBindingService from "./keybindingservice.js";
|
import * as KeyBindingService from "./keybindingservice.js";
|
||||||
import * as MigrationService from "./migrationservice.js";
|
import * as MigrationService from "./migrationservice.js";
|
||||||
import * as SelfUpdateService from "./selfupdateservice.js";
|
import * as SelfUpdateService from "./selfupdateservice.js";
|
||||||
@@ -18,7 +19,6 @@ import * as ThemeService from "./themeservice.js";
|
|||||||
import * as TranslationService from "./translationservice.js";
|
import * as TranslationService from "./translationservice.js";
|
||||||
import * as TrayService from "./trayservice.js";
|
import * as TrayService from "./trayservice.js";
|
||||||
import * as WindowService from "./windowservice.js";
|
import * as WindowService from "./windowservice.js";
|
||||||
import * as WindowSnapService from "./windowsnapservice.js";
|
|
||||||
export {
|
export {
|
||||||
BackupService,
|
BackupService,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
@@ -27,6 +27,7 @@ export {
|
|||||||
DocumentService,
|
DocumentService,
|
||||||
ExtensionService,
|
ExtensionService,
|
||||||
HotkeyService,
|
HotkeyService,
|
||||||
|
HttpClientService,
|
||||||
KeyBindingService,
|
KeyBindingService,
|
||||||
MigrationService,
|
MigrationService,
|
||||||
SelfUpdateService,
|
SelfUpdateService,
|
||||||
@@ -36,8 +37,7 @@ export {
|
|||||||
ThemeService,
|
ThemeService,
|
||||||
TranslationService,
|
TranslationService,
|
||||||
TrayService,
|
TrayService,
|
||||||
WindowService,
|
WindowService
|
||||||
WindowSnapService
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from "./models.js";
|
export * from "./models.js";
|
||||||
|
|||||||
@@ -8,6 +8,114 @@ import {Create as $Create} from "@wailsio/runtime";
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as http$0 from "../../../net/http/models.js";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as time$0 from "../../../time/models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpRequest HTTP请求结构
|
||||||
|
*/
|
||||||
|
export class HttpRequest {
|
||||||
|
"method": string;
|
||||||
|
"url": string;
|
||||||
|
"headers": { [_: string]: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* json, formdata, urlencoded, text
|
||||||
|
*/
|
||||||
|
"bodyType"?: string;
|
||||||
|
"body"?: any;
|
||||||
|
|
||||||
|
/** Creates a new HttpRequest instance. */
|
||||||
|
constructor($$source: Partial<HttpRequest> = {}) {
|
||||||
|
if (!("method" in $$source)) {
|
||||||
|
this["method"] = "";
|
||||||
|
}
|
||||||
|
if (!("url" in $$source)) {
|
||||||
|
this["url"] = "";
|
||||||
|
}
|
||||||
|
if (!("headers" in $$source)) {
|
||||||
|
this["headers"] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HttpRequest instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): HttpRequest {
|
||||||
|
const $$createField2_0 = $$createType0;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("headers" in $$parsedSource) {
|
||||||
|
$$parsedSource["headers"] = $$createField2_0($$parsedSource["headers"]);
|
||||||
|
}
|
||||||
|
return new HttpRequest($$parsedSource as Partial<HttpRequest>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpResponse HTTP响应结构
|
||||||
|
*/
|
||||||
|
export class HttpResponse {
|
||||||
|
/**
|
||||||
|
* 使用resp.Status()返回完整状态如"200 OK"
|
||||||
|
*/
|
||||||
|
"status": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应时间(毫秒)
|
||||||
|
*/
|
||||||
|
"time": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求大小
|
||||||
|
*/
|
||||||
|
"requestSize": string;
|
||||||
|
"body": any;
|
||||||
|
"headers": http$0.Header;
|
||||||
|
"timestamp": time$0.Time;
|
||||||
|
"error"?: any;
|
||||||
|
|
||||||
|
/** Creates a new HttpResponse instance. */
|
||||||
|
constructor($$source: Partial<HttpResponse> = {}) {
|
||||||
|
if (!("status" in $$source)) {
|
||||||
|
this["status"] = "";
|
||||||
|
}
|
||||||
|
if (!("time" in $$source)) {
|
||||||
|
this["time"] = 0;
|
||||||
|
}
|
||||||
|
if (!("requestSize" in $$source)) {
|
||||||
|
this["requestSize"] = "";
|
||||||
|
}
|
||||||
|
if (!("body" in $$source)) {
|
||||||
|
this["body"] = null;
|
||||||
|
}
|
||||||
|
if (!("headers" in $$source)) {
|
||||||
|
this["headers"] = ({} as http$0.Header);
|
||||||
|
}
|
||||||
|
if (!("timestamp" in $$source)) {
|
||||||
|
this["timestamp"] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HttpResponse instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): HttpResponse {
|
||||||
|
const $$createField4_0 = $$createType1;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("headers" in $$parsedSource) {
|
||||||
|
$$parsedSource["headers"] = $$createField4_0($$parsedSource["headers"]);
|
||||||
|
}
|
||||||
|
return new HttpResponse($$parsedSource as Partial<HttpResponse>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MemoryStats 内存统计信息
|
* MemoryStats 内存统计信息
|
||||||
@@ -273,8 +381,8 @@ export class SystemInfo {
|
|||||||
* Creates a new SystemInfo instance from a string or object.
|
* Creates a new SystemInfo instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): SystemInfo {
|
static createFrom($$source: any = {}): SystemInfo {
|
||||||
const $$createField3_0 = $$createType1;
|
const $$createField3_0 = $$createType5;
|
||||||
const $$createField4_0 = $$createType2;
|
const $$createField4_0 = $$createType6;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("osInfo" in $$parsedSource) {
|
if ("osInfo" in $$parsedSource) {
|
||||||
$$parsedSource["osInfo"] = $$createField3_0($$parsedSource["osInfo"]);
|
$$parsedSource["osInfo"] = $$createField3_0($$parsedSource["osInfo"]);
|
||||||
@@ -313,7 +421,7 @@ export class WindowInfo {
|
|||||||
* Creates a new WindowInfo instance from a string or object.
|
* Creates a new WindowInfo instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): WindowInfo {
|
static createFrom($$source: any = {}): WindowInfo {
|
||||||
const $$createField0_0 = $$createType4;
|
const $$createField0_0 = $$createType8;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("Window" in $$parsedSource) {
|
if ("Window" in $$parsedSource) {
|
||||||
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
||||||
@@ -343,8 +451,17 @@ export class WindowSnapService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private type creation functions
|
// Private type creation functions
|
||||||
const $$createType0 = OSInfo.createFrom;
|
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
|
||||||
const $$createType1 = $Create.Nullable($$createType0);
|
var $$createType1 = (function $$initCreateType1(...args): any {
|
||||||
const $$createType2 = $Create.Map($Create.Any, $Create.Any);
|
if ($$createType1 === $$initCreateType1) {
|
||||||
const $$createType3 = application$0.WebviewWindow.createFrom;
|
$$createType1 = $$createType3;
|
||||||
const $$createType4 = $Create.Nullable($$createType3);
|
}
|
||||||
|
return $$createType1(...args);
|
||||||
|
});
|
||||||
|
const $$createType2 = $Create.Array($Create.Any);
|
||||||
|
const $$createType3 = $Create.Map($Create.Any, $$createType2);
|
||||||
|
const $$createType4 = OSInfo.createFrom;
|
||||||
|
const $$createType5 = $Create.Nullable($$createType4);
|
||||||
|
const $$createType6 = $Create.Map($Create.Any, $Create.Any);
|
||||||
|
const $$createType7 = application$0.WebviewWindow.createFrom;
|
||||||
|
const $$createType8 = $Create.Nullable($$createType7);
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WindowSnapService 窗口吸附服务
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleanup 清理资源
|
|
||||||
*/
|
|
||||||
export function Cleanup(): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(2155505498) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GetCurrentThreshold 获取当前自适应阈值(用于调试或显示)
|
|
||||||
*/
|
|
||||||
export function GetCurrentThreshold(): Promise<number> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(3176419026) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OnWindowSnapConfigChanged 处理窗口吸附配置变更
|
|
||||||
*/
|
|
||||||
export function OnWindowSnapConfigChanged(enabled: boolean): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(3794787039, enabled) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RegisterWindow 注册需要吸附管理的窗口
|
|
||||||
*/
|
|
||||||
export function RegisterWindow(documentID: number, window: application$0.WebviewWindow | null, title: string): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(1000222723, documentID, window, title) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ServiceShutdown 实现服务关闭接口
|
|
||||||
*/
|
|
||||||
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(1172710495) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ServiceStartup 服务启动时初始化
|
|
||||||
*/
|
|
||||||
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(2456823262, options) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SetSnapEnabled 设置是否启用窗口吸附
|
|
||||||
*/
|
|
||||||
export function SetSnapEnabled(enabled: boolean): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(2280126835, enabled) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UnregisterWindow 取消注册窗口
|
|
||||||
*/
|
|
||||||
export function UnregisterWindow(documentID: number): Promise<void> & { cancel(): void } {
|
|
||||||
let $resultPromise = $Call.ByID(2844230768, documentID) as any;
|
|
||||||
return $resultPromise;
|
|
||||||
}
|
|
||||||
@@ -5,12 +5,14 @@
|
|||||||
import {Extension} from '@codemirror/state';
|
import {Extension} from '@codemirror/state';
|
||||||
|
|
||||||
import {httpRequestsField, httpRunButtonGutter, httpRunButtonTheme} from './widgets/run-gutter';
|
import {httpRequestsField, httpRunButtonGutter, httpRunButtonTheme} from './widgets/run-gutter';
|
||||||
|
import {responseCacheField} from "@/views/editor/extensions/httpclient/parser/response-inserter";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建 HTTP Client 扩展
|
* 创建 HTTP Client 扩展
|
||||||
*/
|
*/
|
||||||
export function createHttpClientExtension(): Extension[] {
|
export function createHttpClientExtension(): Extension[] {
|
||||||
return [
|
return [
|
||||||
|
responseCacheField,
|
||||||
httpRequestsField,
|
httpRequestsField,
|
||||||
httpRunButtonGutter,
|
httpRunButtonGutter,
|
||||||
httpRunButtonTheme,
|
httpRunButtonTheme,
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
import { EditorView } from '@codemirror/view';
|
import { EditorView } from '@codemirror/view';
|
||||||
import { EditorState, ChangeSpec } from '@codemirror/state';
|
import { EditorState, ChangeSpec, StateField } from '@codemirror/state';
|
||||||
import { syntaxTree } from '@codemirror/language';
|
import { syntaxTree, syntaxTreeAvailable } from '@codemirror/language';
|
||||||
import type { SyntaxNode } from '@lezer/common';
|
import type { SyntaxNode } from '@lezer/common';
|
||||||
import { blockState } from '../../codeblock/state';
|
import { getNoteBlockFromPos } from '../../codeblock/state';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应数据模型
|
* 响应数据模型
|
||||||
*/
|
*/
|
||||||
export interface HttpResponse {
|
export interface HttpResponse {
|
||||||
/** 状态码 */
|
/** 状态码和状态文本,如"200 OK" */
|
||||||
status: number;
|
status: string;
|
||||||
|
|
||||||
/** 状态文本 */
|
|
||||||
statusText: string;
|
|
||||||
|
|
||||||
/** 响应时间(毫秒) */
|
/** 响应时间(毫秒) */
|
||||||
time: number;
|
time: number;
|
||||||
|
|
||||||
|
/** 请求大小 */
|
||||||
|
requestSize?: string;
|
||||||
|
|
||||||
/** 响应体 */
|
/** 响应体 */
|
||||||
body: any;
|
body: any;
|
||||||
|
|
||||||
|
/** 响应头 */
|
||||||
|
headers?: { [_: string]: string[] };
|
||||||
|
|
||||||
/** 时间戳 */
|
/** 时间戳 */
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
|
|
||||||
|
/** 错误信息 */
|
||||||
|
error?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +40,44 @@ const NODE_TYPES = {
|
|||||||
JSON_ARRAY: 'JsonArray',
|
JSON_ARRAY: 'JsonArray',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存接口
|
||||||
|
*/
|
||||||
|
interface ParseCache {
|
||||||
|
version: number;
|
||||||
|
blockId: string;
|
||||||
|
requestPositions: Map<number, {
|
||||||
|
requestNode: SyntaxNode | null;
|
||||||
|
nextRequestPos: number | null;
|
||||||
|
oldResponse: { from: number; to: number } | null;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StateField用于缓存解析结果
|
||||||
|
*/
|
||||||
|
const responseCacheField = StateField.define<ParseCache>({
|
||||||
|
create(): ParseCache {
|
||||||
|
return {
|
||||||
|
version: 0,
|
||||||
|
blockId: '',
|
||||||
|
requestPositions: new Map()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
update(cache, tr): ParseCache {
|
||||||
|
// 如果有文档变更,清空缓存
|
||||||
|
if (tr.docChanged) {
|
||||||
|
return {
|
||||||
|
version: cache.version + 1,
|
||||||
|
blockId: '',
|
||||||
|
requestPositions: new Map()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应插入位置信息
|
* 响应插入位置信息
|
||||||
*/
|
*/
|
||||||
@@ -55,98 +99,119 @@ export class HttpResponseInserter {
|
|||||||
constructor(private view: EditorView) {}
|
constructor(private view: EditorView) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在请求后插入响应数据
|
* 插入HTTP响应(优化版本)
|
||||||
* @param requestPos 请求的起始位置
|
* @param requestPos 请求的起始位置
|
||||||
* @param response 响应数据
|
* @param response 响应数据
|
||||||
*/
|
*/
|
||||||
insertResponse(requestPos: number, response: HttpResponse): void {
|
insertResponse(requestPos: number, response: HttpResponse): void {
|
||||||
const state = this.view.state;
|
const state = this.view.state;
|
||||||
|
|
||||||
|
// 检查语法树是否可用,避免阻塞UI
|
||||||
|
if (!syntaxTreeAvailable(state)) {
|
||||||
|
// 延迟执行,等待语法树可用
|
||||||
|
setTimeout(() => {
|
||||||
|
if (syntaxTreeAvailable(this.view.state)) {
|
||||||
|
this.insertResponse(requestPos, response);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const insertPos = this.findInsertPosition(state, requestPos);
|
const insertPos = this.findInsertPosition(state, requestPos);
|
||||||
|
|
||||||
if (!insertPos) {
|
if (!insertPos) {
|
||||||
console.error('no insert position');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成响应文本
|
// 生成响应文本
|
||||||
const responseText = this.formatResponse(response);
|
const responseText = this.formatResponse(response);
|
||||||
|
|
||||||
// 创建变更
|
// 根据是否有旧响应决定插入内容
|
||||||
|
const insertText = insertPos.hasOldResponse
|
||||||
|
? responseText // 替换旧响应,不需要额外换行
|
||||||
|
: `\n${responseText}`; // 新插入,需要换行分隔
|
||||||
|
|
||||||
const changes: ChangeSpec = {
|
const changes: ChangeSpec = {
|
||||||
from: insertPos.from,
|
from: insertPos.from,
|
||||||
to: insertPos.to,
|
to: insertPos.to,
|
||||||
insert: responseText
|
insert: insertText
|
||||||
};
|
};
|
||||||
|
|
||||||
// 应用变更
|
|
||||||
this.view.dispatch({
|
this.view.dispatch({
|
||||||
changes,
|
changes,
|
||||||
// 将光标移动到插入内容的末尾
|
userEvent: 'http.response.insert',
|
||||||
selection: { anchor: insertPos.from + responseText.length },
|
// 保持光标在请求位置
|
||||||
|
selection: { anchor: requestPos },
|
||||||
|
// 滚动到插入位置
|
||||||
|
scrollIntoView: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找插入位置
|
* 查找插入位置(带缓存优化)
|
||||||
* 规则:
|
|
||||||
* 1. 在当前请求后面
|
|
||||||
* 2. 在下一个请求前面
|
|
||||||
* 3. 如果已有响应(# Response 开头),删除旧响应
|
|
||||||
*/
|
*/
|
||||||
private findInsertPosition(state: EditorState, requestPos: number): InsertPosition | null {
|
private findInsertPosition(state: EditorState, requestPos: number): InsertPosition | null {
|
||||||
|
// 获取当前代码块
|
||||||
|
const blockInfo = getNoteBlockFromPos(state, requestPos);
|
||||||
|
if (!blockInfo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockFrom = blockInfo.range.from;
|
||||||
|
const blockTo = blockInfo.range.to;
|
||||||
|
const blockId = `${blockFrom}-${blockTo}`; // 使用位置作为唯一ID
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
const cache = state.field(responseCacheField, false);
|
||||||
|
if (cache && cache.blockId === blockId) {
|
||||||
|
const cachedResult = cache.requestPositions.get(requestPos);
|
||||||
|
if (cachedResult) {
|
||||||
|
// 使用缓存结果
|
||||||
|
const { requestNode, nextRequestPos, oldResponse } = cachedResult;
|
||||||
|
if (requestNode) {
|
||||||
|
const insertFrom = oldResponse ? oldResponse.from : requestNode.to + 1;
|
||||||
|
const insertTo = oldResponse ? oldResponse.to : insertFrom;
|
||||||
|
return {
|
||||||
|
from: insertFrom,
|
||||||
|
to: insertTo,
|
||||||
|
hasOldResponse: !!oldResponse
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存未命中,执行解析
|
||||||
const tree = syntaxTree(state);
|
const tree = syntaxTree(state);
|
||||||
const blocks = state.field(blockState, false);
|
const context = this.findInsertionContext(tree, state, requestPos, blockFrom, blockTo);
|
||||||
|
|
||||||
if (!blocks) return null;
|
// 更新缓存
|
||||||
|
if (cache) {
|
||||||
|
cache.blockId = blockId;
|
||||||
|
cache.requestPositions.set(requestPos, context);
|
||||||
|
}
|
||||||
|
|
||||||
// 找到当前 HTTP 块
|
if (!context.requestNode) {
|
||||||
const currentBlock = blocks.find(block =>
|
return null;
|
||||||
block.language.name === 'http' &&
|
}
|
||||||
block.content.from <= requestPos &&
|
|
||||||
requestPos <= block.content.to
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!currentBlock) return null;
|
// 计算插入位置
|
||||||
|
let insertFrom: number;
|
||||||
const context = this.findInsertionContext(
|
let insertTo: number;
|
||||||
tree,
|
let hasOldResponse = false;
|
||||||
state,
|
|
||||||
requestPos,
|
|
||||||
currentBlock.content.from,
|
|
||||||
currentBlock.content.to
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!context.requestNode) return null;
|
|
||||||
|
|
||||||
const requestEnd = context.requestNode.to;
|
|
||||||
|
|
||||||
if (context.oldResponse) {
|
if (context.oldResponse) {
|
||||||
// 如果有旧响应,精确替换(从上一行的末尾到响应末尾)
|
// 有旧响应,替换
|
||||||
const oldResponseStartLine = state.doc.lineAt(context.oldResponse.from);
|
insertFrom = context.oldResponse.from;
|
||||||
const prevLineNum = oldResponseStartLine.number - 1;
|
insertTo = context.oldResponse.to;
|
||||||
|
hasOldResponse = true;
|
||||||
let deleteFrom = context.oldResponse.from;
|
|
||||||
if (prevLineNum >= 1) {
|
|
||||||
const prevLine = state.doc.line(prevLineNum);
|
|
||||||
deleteFrom = prevLine.to; // 从上一行的末尾开始删除
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
from: deleteFrom,
|
|
||||||
to: context.oldResponse.to,
|
|
||||||
hasOldResponse: true
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
// 如果没有旧响应,在请求后面插入
|
// 没有旧响应,在请求后插入
|
||||||
const requestEndLine = state.doc.lineAt(requestEnd);
|
const requestEndLine = state.doc.lineAt(context.requestNode.to);
|
||||||
|
// 在请求行末尾插入,添加换行符分隔
|
||||||
// 在当前行末尾插入(formatResponse 会自动添加必要的换行)
|
insertFrom = requestEndLine.to;
|
||||||
return {
|
insertTo = insertFrom;
|
||||||
from: requestEndLine.to,
|
|
||||||
to: requestEndLine.to,
|
|
||||||
hasOldResponse: false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { from: insertFrom, to: insertTo, hasOldResponse };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -216,13 +281,18 @@ export class HttpResponseInserter {
|
|||||||
// 查找响应注释
|
// 查找响应注释
|
||||||
if (!responseStartNode && forwardCursor.name === NODE_TYPES.LINE_COMMENT) {
|
if (!responseStartNode && forwardCursor.name === NODE_TYPES.LINE_COMMENT) {
|
||||||
const commentText = state.doc.sliceString(forwardCursor.from, forwardCursor.to);
|
const commentText = state.doc.sliceString(forwardCursor.from, forwardCursor.to);
|
||||||
// 避免不必要的 trim
|
// 避免不必要的 trim,同时识别普通响应和错误响应
|
||||||
if (commentText.startsWith('# Response') || commentText.startsWith(' # Response')) {
|
if (commentText.startsWith('# Response') || commentText.startsWith(' # Response')) {
|
||||||
const startNode = forwardCursor.node;
|
const startNode = forwardCursor.node;
|
||||||
responseStartNode = startNode;
|
responseStartNode = startNode;
|
||||||
foundResponse = true;
|
foundResponse = true;
|
||||||
|
|
||||||
// 继续查找 JSON 和结束分隔线
|
// 检查是否为错误响应(只有一行)
|
||||||
|
if (commentText.includes('Error:')) {
|
||||||
|
// 错误响应只有一行,直接设置结束位置
|
||||||
|
responseEndPos = startNode.to;
|
||||||
|
} else {
|
||||||
|
// 继续查找 JSON 和结束分隔线(正常响应)
|
||||||
let nextNode = startNode.nextSibling;
|
let nextNode = startNode.nextSibling;
|
||||||
while (nextNode && nextNode.from < (nextRequestPos || blockTo)) {
|
while (nextNode && nextNode.from < (nextRequestPos || blockTo)) {
|
||||||
// 找到 JSON
|
// 找到 JSON
|
||||||
@@ -254,6 +324,7 @@ export class HttpResponseInserter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} while (forwardCursor.next() && forwardCursor.from < blockTo);
|
} while (forwardCursor.next() && forwardCursor.from < blockTo);
|
||||||
|
|
||||||
// 构建旧响应信息
|
// 构建旧响应信息
|
||||||
@@ -277,14 +348,22 @@ export class HttpResponseInserter {
|
|||||||
* 格式化响应数据
|
* 格式化响应数据
|
||||||
*/
|
*/
|
||||||
private formatResponse(response: HttpResponse): string {
|
private formatResponse(response: HttpResponse): string {
|
||||||
|
// 如果有错误,使用最简洁的错误格式
|
||||||
|
if (response.error) {
|
||||||
|
return `# Response Error: ${response.error}`;
|
||||||
|
}
|
||||||
|
// 正常响应格式
|
||||||
const timestamp = response.timestamp || new Date();
|
const timestamp = response.timestamp || new Date();
|
||||||
const dateStr = this.formatTimestamp(timestamp);
|
const dateStr = this.formatTimestamp(timestamp);
|
||||||
|
|
||||||
// 构建响应头行(不带分隔符)
|
let headerLine = `# Response ${response.status} ${response.time}ms`;
|
||||||
const headerLine = `# Response ${response.status} ${response.statusText} ${response.time}ms ${dateStr}`;
|
if (response.requestSize) {
|
||||||
|
headerLine += ` ${response.requestSize}`;
|
||||||
|
}
|
||||||
|
headerLine += ` ${dateStr}`;
|
||||||
|
|
||||||
// 完整的开头行(只有响应头,不带分隔符)
|
// 完整的开头行(不添加前导换行符)
|
||||||
const header = `\n${headerLine}\n`;
|
const header = `${headerLine}\n`;
|
||||||
|
|
||||||
// 格式化响应体
|
// 格式化响应体
|
||||||
let body: string;
|
let body: string;
|
||||||
@@ -299,15 +378,15 @@ export class HttpResponseInserter {
|
|||||||
}
|
}
|
||||||
} else if (response.body === null || response.body === undefined) {
|
} else if (response.body === null || response.body === undefined) {
|
||||||
// 空响应(只有响应头和结束分隔线)
|
// 空响应(只有响应头和结束分隔线)
|
||||||
const endLine = `# ${'-'.repeat(headerLine.length - 2)}`; // 减去 "# " 的长度
|
const endLine = `# ${'-'.repeat(Math.max(16, headerLine.length - 2))}`; // 最小16个字符
|
||||||
return header + endLine;
|
return header + endLine;
|
||||||
} else {
|
} else {
|
||||||
// 对象或数组
|
// 对象或数组
|
||||||
body = JSON.stringify(response.body, null, 2);
|
body = JSON.stringify(response.body, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 结尾分隔线:和响应头行长度完全一致
|
// 结尾分隔线:和响应头行长度一致,最小16个字符
|
||||||
const endLine = `# ${'-'.repeat(headerLine.length - 2)}`; // 减去 "# " 的长度
|
const endLine = `# ${'-'.repeat(Math.max(16, headerLine.length - 2))}`;
|
||||||
|
|
||||||
return header + body + `\n${endLine}`;
|
return header + body + `\n${endLine}`;
|
||||||
}
|
}
|
||||||
@@ -335,3 +414,8 @@ export function insertHttpResponse(view: EditorView, requestPos: number, respons
|
|||||||
inserter.insertResponse(requestPos, response);
|
inserter.insertResponse(requestPos, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出StateField用于扩展配置
|
||||||
|
*/
|
||||||
|
export { responseCacheField };
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { blockState } from '../../codeblock/state';
|
|||||||
import { parseHttpRequest, type HttpRequest } from '../parser/request-parser';
|
import { parseHttpRequest, type HttpRequest } from '../parser/request-parser';
|
||||||
import { insertHttpResponse, type HttpResponse } from '../parser/response-inserter';
|
import { insertHttpResponse, type HttpResponse } from '../parser/response-inserter';
|
||||||
import { createDebounce } from '@/common/utils/debounce';
|
import { createDebounce } from '@/common/utils/debounce';
|
||||||
|
import { ExecuteRequest } from '@/../bindings/voidraft/internal/services/httpclientservice';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 语法树节点类型常量
|
* 语法树节点类型常量
|
||||||
@@ -156,34 +157,36 @@ class RunButtonMarker extends GutterMarker {
|
|||||||
this.setLoadingState(true);
|
this.setLoadingState(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`\n============ 执行 HTTP 请求 ============`);
|
const response = await ExecuteRequest(this.cachedRequest);
|
||||||
console.log('行号:', this.lineNumber);
|
if (!response) {
|
||||||
console.log('解析结果:', JSON.stringify(this.cachedRequest, null, 2));
|
throw new Error('No response');
|
||||||
|
|
||||||
// TODO: 调用后端 API 执行请求
|
|
||||||
// 临时模拟网络延迟
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000));
|
|
||||||
|
|
||||||
// 临时模拟响应数据用于测试
|
|
||||||
const mockResponse: HttpResponse = {
|
|
||||||
status: 200,
|
|
||||||
statusText: 'OK',
|
|
||||||
time: Math.floor(Math.random() * 500) + 50, // 50-550ms
|
|
||||||
body: {
|
|
||||||
code: 200,
|
|
||||||
message: "请求成功",
|
|
||||||
data: {
|
|
||||||
id: 1001,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
timestamp: new Date()
|
// 转换后端响应为前端格式
|
||||||
|
const httpResponse: HttpResponse = {
|
||||||
|
status: response.status, // 后端已返回完整状态如"200 OK"
|
||||||
|
time: response.time,
|
||||||
|
requestSize: response.requestSize,
|
||||||
|
body: response.body,
|
||||||
|
headers: response.headers,
|
||||||
|
timestamp: response.timestamp ? new Date(response.timestamp) : new Date(),
|
||||||
|
error: response.error
|
||||||
};
|
};
|
||||||
|
|
||||||
// 插入响应数据
|
// 插入响应数据
|
||||||
insertHttpResponse(view, this.cachedRequest.position.from, mockResponse);
|
insertHttpResponse(view, this.cachedRequest.position.from, httpResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('HTTP 请求执行失败:', error);
|
// 创建错误响应
|
||||||
|
const errorResponse: HttpResponse = {
|
||||||
|
status: 'Request Failed',
|
||||||
|
time: 0,
|
||||||
|
body: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
timestamp: new Date(),
|
||||||
|
error: error
|
||||||
|
};
|
||||||
|
|
||||||
|
// 插入错误响应
|
||||||
|
insertHttpResponse(view, this.cachedRequest.position.from, errorResponse);
|
||||||
} finally {
|
} finally {
|
||||||
this.setLoadingState(false);
|
this.setLoadingState(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@codemirror/view';
|
} from '@codemirror/view';
|
||||||
import { Extension, Range } from '@codemirror/state';
|
import { Extension, Range } from '@codemirror/state';
|
||||||
import * as runtime from "@wailsio/runtime";
|
import * as runtime from "@wailsio/runtime";
|
||||||
|
import { getNoteBlockFromPos } from '../codeblock/state';
|
||||||
const pathStr = `<svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"><path d="M607.934444 417.856853c-6.179746-6.1777-12.766768-11.746532-19.554358-16.910135l-0.01228 0.011256c-6.986111-6.719028-16.47216-10.857279-26.930349-10.857279-21.464871 0-38.864146 17.400299-38.864146 38.864146 0 9.497305 3.411703 18.196431 9.071609 24.947182l-0.001023 0c0.001023 0.001023 0.00307 0.00307 0.005117 0.004093 2.718925 3.242857 5.953595 6.03853 9.585309 8.251941 3.664459 3.021823 7.261381 5.997598 10.624988 9.361205l3.203972 3.204995c40.279379 40.229237 28.254507 109.539812-12.024871 149.820214L371.157763 796.383956c-40.278355 40.229237-105.761766 40.229237-146.042167 0l-3.229554-3.231601c-40.281425-40.278355-40.281425-105.809861 0-145.991002l75.93546-75.909877c9.742898-7.733125 15.997346-19.668968 15.997346-33.072233 0-23.312962-18.898419-42.211381-42.211381-42.211381-8.797363 0-16.963347 2.693342-23.725354 7.297197-0.021489-0.045025-0.044002-0.088004-0.066515-0.134053l-0.809435 0.757247c-2.989077 2.148943-5.691629 4.669346-8.025791 7.510044l-78.913281 73.841775c-74.178443 74.229608-74.178443 195.632609 0 269.758863l3.203972 3.202948c74.178443 74.127278 195.529255 74.127278 269.707698 0l171.829484-171.880649c74.076112-74.17435 80.357166-191.184297 6.282077-265.311575L607.934444 417.856853z"></path><path d="M855.61957 165.804257l-3.203972-3.203972c-74.17742-74.178443-195.528232-74.178443-269.706675 0L410.87944 334.479911c-74.178443 74.178443-78.263481 181.296089-4.085038 255.522628l3.152806 3.104711c3.368724 3.367701 6.865361 6.54302 10.434653 9.588379 2.583848 2.885723 5.618974 5.355985 8.992815 7.309476 0.025583 0.020466 0.052189 0.041956 0.077771 0.062422l0.011256-0.010233c5.377474 3.092431 11.608386 4.870938 18.257829 4.870938 20.263509 0 36.68962-16.428158 36.68962-36.68962 0-5.719258-1.309832-11.132548-3.645017-15.95846l0 0c-4.850471-10.891048-13.930267-17.521049-20.210297-23.802102l-3.15383-3.102664c-40.278355-40.278355-24.982998-98.79612 15.295358-139.074476l171.930791-171.830507c40.179095-40.280402 105.685018-40.280402 145.965419 0l3.206018 3.152806c40.279379 40.281425 40.279379 105.838513 0 146.06775l-75.686796 75.737962c-10.296507 7.628748-16.97358 19.865443-16.97358 33.662681 0 23.12365 18.745946 41.87062 41.87062 41.87062 8.048303 0 15.563464-2.275833 21.944801-6.211469 0.048095 0.081864 0.093121 0.157589 0.141216 0.240477l1.173732-1.083681c3.616364-2.421142 6.828522-5.393847 9.529027-8.792247l79.766718-73.603345C929.798013 361.334535 929.798013 239.981676 855.61957 165.804257z"></path></svg>`;
|
const pathStr = `<svg viewBox="0 0 1024 1024" width="16" height="16" fill="currentColor"><path d="M607.934444 417.856853c-6.179746-6.1777-12.766768-11.746532-19.554358-16.910135l-0.01228 0.011256c-6.986111-6.719028-16.47216-10.857279-26.930349-10.857279-21.464871 0-38.864146 17.400299-38.864146 38.864146 0 9.497305 3.411703 18.196431 9.071609 24.947182l-0.001023 0c0.001023 0.001023 0.00307 0.00307 0.005117 0.004093 2.718925 3.242857 5.953595 6.03853 9.585309 8.251941 3.664459 3.021823 7.261381 5.997598 10.624988 9.361205l3.203972 3.204995c40.279379 40.229237 28.254507 109.539812-12.024871 149.820214L371.157763 796.383956c-40.278355 40.229237-105.761766 40.229237-146.042167 0l-3.229554-3.231601c-40.281425-40.278355-40.281425-105.809861 0-145.991002l75.93546-75.909877c9.742898-7.733125 15.997346-19.668968 15.997346-33.072233 0-23.312962-18.898419-42.211381-42.211381-42.211381-8.797363 0-16.963347 2.693342-23.725354 7.297197-0.021489-0.045025-0.044002-0.088004-0.066515-0.134053l-0.809435 0.757247c-2.989077 2.148943-5.691629 4.669346-8.025791 7.510044l-78.913281 73.841775c-74.178443 74.229608-74.178443 195.632609 0 269.758863l3.203972 3.202948c74.178443 74.127278 195.529255 74.127278 269.707698 0l171.829484-171.880649c74.076112-74.17435 80.357166-191.184297 6.282077-265.311575L607.934444 417.856853z"></path><path d="M855.61957 165.804257l-3.203972-3.203972c-74.17742-74.178443-195.528232-74.178443-269.706675 0L410.87944 334.479911c-74.178443 74.178443-78.263481 181.296089-4.085038 255.522628l3.152806 3.104711c3.368724 3.367701 6.865361 6.54302 10.434653 9.588379 2.583848 2.885723 5.618974 5.355985 8.992815 7.309476 0.025583 0.020466 0.052189 0.041956 0.077771 0.062422l0.011256-0.010233c5.377474 3.092431 11.608386 4.870938 18.257829 4.870938 20.263509 0 36.68962-16.428158 36.68962-36.68962 0-5.719258-1.309832-11.132548-3.645017-15.95846l0 0c-4.850471-10.891048-13.930267-17.521049-20.210297-23.802102l-3.15383-3.102664c-40.278355-40.278355-24.982998-98.79612 15.295358-139.074476l171.930791-171.830507c40.179095-40.280402 105.685018-40.280402 145.965419 0l3.206018 3.152806c40.279379 40.281425 40.279379 105.838513 0 146.06775l-75.686796 75.737962c-10.296507 7.628748-16.97358 19.865443-16.97358 33.662681 0 23.12365 18.745946 41.87062 41.87062 41.87062 8.048303 0 15.563464-2.275833 21.944801-6.211469 0.048095 0.081864 0.093121 0.157589 0.141216 0.240477l1.173732-1.083681c3.616364-2.421142 6.828522-5.393847 9.529027-8.792247l79.766718-73.603345C929.798013 361.334535 929.798013 239.981676 855.61957 165.804257z"></path></svg>`;
|
||||||
const defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#].[^\s]*)\b/gi;
|
const defaultRegexp = /\b((?:https?|ftp):\/\/[^\s/$.?#].[^\s]*)\b/gi;
|
||||||
|
|
||||||
@@ -53,6 +54,13 @@ function hyperLinkDecorations(view: EditorView, anchor?: HyperLinkExtensionOptio
|
|||||||
const from = match.index;
|
const from = match.index;
|
||||||
const to = from + match[0].length;
|
const to = from + match[0].length;
|
||||||
|
|
||||||
|
// 检查当前位置是否在 HTTP 代码块中
|
||||||
|
const block = getNoteBlockFromPos(view.state, from);
|
||||||
|
if (block && block.language.name === 'http') {
|
||||||
|
// 如果在 HTTP 代码块中,跳过超链接装饰
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const linkMark = Decoration.mark({
|
const linkMark = Decoration.mark({
|
||||||
class: 'cm-hyper-link-text',
|
class: 'cm-hyper-link-text',
|
||||||
attributes: {
|
attributes: {
|
||||||
@@ -84,6 +92,13 @@ const linkDecorator = (
|
|||||||
new MatchDecorator({
|
new MatchDecorator({
|
||||||
regexp: regexp || defaultRegexp,
|
regexp: regexp || defaultRegexp,
|
||||||
decorate: (add, from, to, match, view) => {
|
decorate: (add, from, to, match, view) => {
|
||||||
|
// 检查当前位置是否在 HTTP 代码块中
|
||||||
|
const block = getNoteBlockFromPos(view.state, from);
|
||||||
|
if (block && block.language.name === 'http') {
|
||||||
|
// 如果在 HTTP 代码块中,跳过超链接装饰
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const url = match[0];
|
const url = match[0];
|
||||||
let urlStr = matchFn && typeof matchFn === 'function' ? matchFn(url, match.input, from, to) : url;
|
let urlStr = matchFn && typeof matchFn === 'function' ? matchFn(url, match.input, from, to) : url;
|
||||||
if (matchData && matchData[url]) {
|
if (matchData && matchData[url]) {
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -15,6 +15,7 @@ require (
|
|||||||
golang.org/x/sys v0.37.0
|
golang.org/x/sys v0.37.0
|
||||||
golang.org/x/text v0.30.0
|
golang.org/x/text v0.30.0
|
||||||
modernc.org/sqlite v1.39.1
|
modernc.org/sqlite v1.39.1
|
||||||
|
resty.dev/v3 v3.0.0-beta.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -262,3 +262,5 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
|||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
resty.dev/v3 v3.0.0-beta.3 h1:3kEwzEgCnnS6Ob4Emlk94t+I/gClyoah7SnNi67lt+E=
|
||||||
|
resty.dev/v3 v3.0.0-beta.3/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4=
|
||||||
|
|||||||
164
internal/services/httpclient_service.go
Normal file
164
internal/services/httpclient_service.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||||
|
"resty.dev/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HttpClientService HTTP客户端服务
|
||||||
|
type HttpClientService struct {
|
||||||
|
logger *log.LogService
|
||||||
|
client *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpRequest HTTP请求结构
|
||||||
|
type HttpRequest struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
BodyType string `json:"bodyType,omitempty"` // json, formdata, urlencoded, text
|
||||||
|
Body interface{} `json:"body,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpResponse HTTP响应结构
|
||||||
|
type HttpResponse struct {
|
||||||
|
Status string `json:"status"` // 使用resp.Status()返回完整状态如"200 OK"
|
||||||
|
Time int64 `json:"time"` // 响应时间(毫秒)
|
||||||
|
RequestSize string `json:"requestSize"` // 请求大小
|
||||||
|
Body interface{} `json:"body"`
|
||||||
|
Headers http.Header `json:"headers"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
Error interface{} `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHttpClientService 创建新的HTTP客户端服务实例
|
||||||
|
func NewHttpClientService(logger *log.LogService) *HttpClientService {
|
||||||
|
client := resty.New().
|
||||||
|
SetTimeout(30 * time.Second).
|
||||||
|
SetRetryCount(0).
|
||||||
|
EnableTrace().
|
||||||
|
SetHeaders(map[string]string{
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
"Access-Control-Allow-Methods": "*",
|
||||||
|
"Access-Control-Allow-Headers": "*",
|
||||||
|
"Access-Control-Allow-Credentials": "true",
|
||||||
|
})
|
||||||
|
|
||||||
|
return &HttpClientService{
|
||||||
|
logger: logger,
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteRequest 执行HTTP请求
|
||||||
|
func (hcs *HttpClientService) ExecuteRequest(ctx context.Context, request *HttpRequest) (*HttpResponse, error) {
|
||||||
|
// 创建请求
|
||||||
|
req := hcs.client.R().SetContext(ctx)
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
if request.Headers != nil {
|
||||||
|
req.SetHeaders(request.Headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求体
|
||||||
|
if err := hcs.setRequestBody(req, request); err != nil {
|
||||||
|
return nil, fmt.Errorf("set request body failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行请求
|
||||||
|
resp, err := req.Execute(strings.ToUpper(request.Method), request.URL)
|
||||||
|
|
||||||
|
// 构建响应对象
|
||||||
|
return hcs.buildResponse(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setRequestBody 设置请求体
|
||||||
|
func (hcs *HttpClientService) setRequestBody(req *resty.Request, request *HttpRequest) error {
|
||||||
|
if request.Body == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(request.BodyType) {
|
||||||
|
case "json":
|
||||||
|
req.SetHeader("Content-Type", "application/json")
|
||||||
|
req.SetBody(request.Body)
|
||||||
|
case "formdata":
|
||||||
|
if formData, ok := request.Body.(map[string]interface{}); ok {
|
||||||
|
for key, value := range formData {
|
||||||
|
req.SetFormData(map[string]string{key: fmt.Sprintf("%v", value)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "urlencoded":
|
||||||
|
req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if formData, ok := request.Body.(map[string]interface{}); ok {
|
||||||
|
values := url.Values{}
|
||||||
|
for key, value := range formData {
|
||||||
|
values.Set(key, fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
req.SetBody(values.Encode())
|
||||||
|
}
|
||||||
|
case "text":
|
||||||
|
req.SetHeader("Content-Type", "text/plain")
|
||||||
|
req.SetBody(fmt.Sprintf("%v", request.Body))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported body type: %s", request.BodyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildResponse 构建响应对象
|
||||||
|
func (hcs *HttpClientService) buildResponse(resp *resty.Response, err error) (*HttpResponse, error) {
|
||||||
|
httpResp := &HttpResponse{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有错误,设置错误状态并返回
|
||||||
|
if err != nil {
|
||||||
|
httpResp.Status = "Request Failed"
|
||||||
|
httpResp.Error = err.Error()
|
||||||
|
return httpResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置基本响应信息
|
||||||
|
httpResp.Status = resp.Status()
|
||||||
|
httpResp.Time = resp.Duration().Milliseconds()
|
||||||
|
httpResp.RequestSize = hcs.formatBytes(resp.Size())
|
||||||
|
|
||||||
|
if errorData := resp.Error(); errorData != nil {
|
||||||
|
httpResp.Error = errorData
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
httpResp.Headers = resp.Header()
|
||||||
|
httpResp.Body = resp.String()
|
||||||
|
|
||||||
|
return httpResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatBytes 格式化字节大小
|
||||||
|
func (hcs *HttpClientService) formatBytes(bytes int64) string {
|
||||||
|
if bytes < 0 {
|
||||||
|
return "0 B"
|
||||||
|
}
|
||||||
|
|
||||||
|
const unit = 1024
|
||||||
|
if bytes < unit {
|
||||||
|
return fmt.Sprintf("%d B", bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
div, exp := int64(unit), 0
|
||||||
|
for n := bytes / unit; n >= unit; n /= unit {
|
||||||
|
div *= unit
|
||||||
|
exp++
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ type ServiceManager struct {
|
|||||||
notificationService *notifications.NotificationService
|
notificationService *notifications.NotificationService
|
||||||
testService *TestService // 测试服务(仅开发环境)
|
testService *TestService // 测试服务(仅开发环境)
|
||||||
BackupService *BackupService
|
BackupService *BackupService
|
||||||
|
httpClientService *HttpClientService // HTTP客户端服务
|
||||||
logger *log.LogService
|
logger *log.LogService
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +103,9 @@ func NewServiceManager() *ServiceManager {
|
|||||||
// 初始化备份服务
|
// 初始化备份服务
|
||||||
backupService := NewBackupService(configService, databaseService, logger)
|
backupService := NewBackupService(configService, databaseService, logger)
|
||||||
|
|
||||||
|
// 初始化HTTP客户端服务
|
||||||
|
httpClientService := NewHttpClientService(logger)
|
||||||
|
|
||||||
// 初始化测试服务(开发环境使用)
|
// 初始化测试服务(开发环境使用)
|
||||||
testService := NewTestService(badgeService, notificationService, logger)
|
testService := NewTestService(badgeService, notificationService, logger)
|
||||||
|
|
||||||
@@ -142,7 +146,6 @@ func NewServiceManager() *ServiceManager {
|
|||||||
databaseService: databaseService,
|
databaseService: databaseService,
|
||||||
documentService: documentService,
|
documentService: documentService,
|
||||||
windowService: windowService,
|
windowService: windowService,
|
||||||
windowSnapService: windowSnapService,
|
|
||||||
migrationService: migrationService,
|
migrationService: migrationService,
|
||||||
systemService: systemService,
|
systemService: systemService,
|
||||||
hotkeyService: hotkeyService,
|
hotkeyService: hotkeyService,
|
||||||
@@ -158,6 +161,7 @@ func NewServiceManager() *ServiceManager {
|
|||||||
notificationService: notificationService,
|
notificationService: notificationService,
|
||||||
testService: testService,
|
testService: testService,
|
||||||
BackupService: backupService,
|
BackupService: backupService,
|
||||||
|
httpClientService: httpClientService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +173,6 @@ func (sm *ServiceManager) GetServices() []application.Service {
|
|||||||
application.NewService(sm.databaseService),
|
application.NewService(sm.databaseService),
|
||||||
application.NewService(sm.documentService),
|
application.NewService(sm.documentService),
|
||||||
application.NewService(sm.windowService),
|
application.NewService(sm.windowService),
|
||||||
application.NewService(sm.windowSnapService),
|
|
||||||
application.NewService(sm.keyBindingService),
|
application.NewService(sm.keyBindingService),
|
||||||
application.NewService(sm.extensionService),
|
application.NewService(sm.extensionService),
|
||||||
application.NewService(sm.migrationService),
|
application.NewService(sm.migrationService),
|
||||||
@@ -185,6 +188,7 @@ func (sm *ServiceManager) GetServices() []application.Service {
|
|||||||
application.NewService(sm.notificationService),
|
application.NewService(sm.notificationService),
|
||||||
application.NewService(sm.testService),
|
application.NewService(sm.testService),
|
||||||
application.NewService(sm.BackupService),
|
application.NewService(sm.BackupService),
|
||||||
|
application.NewService(sm.httpClientService),
|
||||||
}
|
}
|
||||||
return services
|
return services
|
||||||
}
|
}
|
||||||
@@ -273,3 +277,8 @@ func (sm *ServiceManager) GetNotificationService() *notifications.NotificationSe
|
|||||||
func (sm *ServiceManager) GetSystemService() *SystemService {
|
func (sm *ServiceManager) GetSystemService() *SystemService {
|
||||||
return sm.systemService
|
return sm.systemService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHttpClientService 获取HTTP客户端服务
|
||||||
|
func (sm *ServiceManager) GetHttpClientService() *HttpClientService {
|
||||||
|
return sm.httpClientService
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user