dsh-vscode-mode 0.1.52 → 0.1.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/assets/shell/dsh-open.cs +182 -0
- package/assets/shell/dsh-open.ps1 +52 -0
- package/assets/shell/dsh-open.sh +70 -0
- package/assets/shell/dsh-whale.ico +0 -0
- package/lib/client.js +718 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +1086 -5
- package/lib/index.js.map +1 -1
- package/package.json +2 -1
- package/src/client/chooseDialog.ts +70 -0
- package/src/client/externalOpen.ts +130 -0
- package/src/client/index.ts +4 -0
- package/src/client/openFlow.ts +330 -0
- package/src/client/ui/IntegrationSettings.ts +243 -0
- package/src/client/ui/McpSettings.ts +2 -0
- package/src/externalHandoff.ts +70 -0
- package/src/fileOpenSettings.ts +10 -2
- package/src/index.ts +3 -0
- package/src/integrate.ts +576 -0
- package/src/rpc.ts +90 -3
- package/src/shared/externalOpen.ts +62 -0
- package/src/shared/integration.ts +73 -0
- package/src/shared/rpc.ts +26 -3
- package/src/unityBridge.ts +234 -0
- package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +267 -0
- package/unity/com.dsh.editor/Editor/DshCodeEditor.cs.meta +11 -0
- package/unity/com.dsh.editor/Editor/com.dsh.editor.asmdef +15 -0
- package/unity/com.dsh.editor/Editor/com.dsh.editor.asmdef.meta +7 -0
- package/unity/com.dsh.editor/Editor.meta +8 -0
- package/unity/com.dsh.editor/README.md +33 -0
- package/unity/com.dsh.editor/README.md.meta +7 -0
- package/unity/com.dsh.editor/package.json +7 -0
- package/unity/com.dsh.editor/package.json.meta +7 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// DshCodeEditor.cs — DSH 文件编辑 Unity 外部脚本编辑器集成。
|
|
2
|
+
// 机制:[InitializeOnLoad] 注册 IExternalCodeEditor,Preferences → External Tools 的
|
|
3
|
+
// External Script Editor 下拉出现「DSH 文件编辑」(虚拟安装,无本地 exe)。
|
|
4
|
+
// OpenProject:优先把打开请求移交 host(edrv.external.handoff)——已打开的 DSH 页面
|
|
5
|
+
// (3s 移交轮询)就地执行打开规则,不重复开新页;无活跃页面/2s 未领取(页面刚关)则
|
|
6
|
+
// 回退 Application.OpenURL 深链。移交全程后台线程(不阻塞编辑器主线程),
|
|
7
|
+
// 回退开浏览器经 EditorApplication.delayCall 切回主线程。
|
|
8
|
+
// 深链契约见插件 src/shared/externalOpen.ts(edrvOpen/edrvPaths/edrvLine/edrvColumn)。
|
|
9
|
+
// 作者 ddj 2026-09-08
|
|
10
|
+
using System;
|
|
11
|
+
using System.IO;
|
|
12
|
+
using System.Net;
|
|
13
|
+
using System.Text;
|
|
14
|
+
using System.Threading;
|
|
15
|
+
using UnityEditor;
|
|
16
|
+
using UnityEngine;
|
|
17
|
+
using Unity.CodeEditor;
|
|
18
|
+
|
|
19
|
+
namespace Dsh.EditorIntegration
|
|
20
|
+
{
|
|
21
|
+
[InitializeOnLoad]
|
|
22
|
+
public class DshCodeEditor : IExternalCodeEditor
|
|
23
|
+
{
|
|
24
|
+
private const string EditorName = "DSH 文件编辑";
|
|
25
|
+
private const string VirtualPath = "dsh-editor://vscode-mode";
|
|
26
|
+
private const string BaseUrlPref = "DshEditor.BaseUrl";
|
|
27
|
+
private const string DefaultBaseUrl = "http://127.0.0.1:3080";
|
|
28
|
+
private const string DshVersion = "0.2.3";
|
|
29
|
+
internal const string VersionText = DshVersion;
|
|
30
|
+
|
|
31
|
+
static DshCodeEditor()
|
|
32
|
+
{
|
|
33
|
+
try
|
|
34
|
+
{
|
|
35
|
+
CodeEditor.Register(new DshCodeEditor());
|
|
36
|
+
Debug.Log("[DshCodeEditor] 已注册为 Unity 外部脚本编辑器(v" + DshVersion + ")");
|
|
37
|
+
}
|
|
38
|
+
catch (Exception e)
|
|
39
|
+
{
|
|
40
|
+
Debug.LogError("[DshCodeEditor] 注册失败:" + e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public CodeEditor.Installation[] Installations
|
|
45
|
+
{
|
|
46
|
+
// Path 指向真实存在的 Unity.exe:部分 Unity 版本会过滤「路径不存在」的安装条目;
|
|
47
|
+
// 打开逻辑全在本类(OpenProject),该路径仅作占位与选中态回读。
|
|
48
|
+
get { return new[] { new CodeEditor.Installation { Name = EditorName, Path = RealPath() } }; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// <summary>本包声明的安装路径(真实存在的 Unity.exe;取不到时回退虚拟路径)。</summary>
|
|
52
|
+
internal static string RealPath()
|
|
53
|
+
{
|
|
54
|
+
try
|
|
55
|
+
{
|
|
56
|
+
var path = EditorApplication.applicationPath;
|
|
57
|
+
return string.IsNullOrEmpty(path) ? VirtualPath : path;
|
|
58
|
+
}
|
|
59
|
+
catch (Exception)
|
|
60
|
+
{
|
|
61
|
+
return VirtualPath;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public void Initialize(string editorInstallationPath)
|
|
66
|
+
{
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public void OnGUI()
|
|
70
|
+
{
|
|
71
|
+
var url = EditorGUILayout.TextField("DSH 服务地址", EditorPrefs.GetString(BaseUrlPref, DefaultBaseUrl));
|
|
72
|
+
if (GUI.changed) EditorPrefs.SetString(BaseUrlPref, url.Trim().TrimEnd('/'));
|
|
73
|
+
if (GUILayout.Button("测试:在 DSH 中打开当前项目")) BeginOpen(ResolveAbsolute(Application.dataPath), 0, 0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public void SyncAll()
|
|
77
|
+
{
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles)
|
|
81
|
+
{
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation)
|
|
85
|
+
{
|
|
86
|
+
if (string.Equals(editorPath, VirtualPath, StringComparison.OrdinalIgnoreCase)
|
|
87
|
+
|| string.Equals(editorPath, RealPath(), StringComparison.OrdinalIgnoreCase))
|
|
88
|
+
{
|
|
89
|
+
installation = Installations[0];
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
installation = new CodeEditor.Installation();
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public bool OpenProject(string path, int line, int column)
|
|
97
|
+
{
|
|
98
|
+
// Open C# Project(Assets 菜单)传入空路径:视为打开 Unity 项目根(按文件夹规则路由)
|
|
99
|
+
var target = string.IsNullOrEmpty(path) ? ProjectRoot() : ResolveAbsolute(path);
|
|
100
|
+
BeginOpen(target, line, column);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// <summary>
|
|
105
|
+
/// 后台线程执行移交/回退(不阻塞编辑器主线程)。
|
|
106
|
+
/// ⚠️ EditorPrefs 仅主线程可读:baseUrl 必须在主线程(BeginOpen 调用点)读好后闭包捕获,
|
|
107
|
+
/// 后台线程只做网络 IO 与纯计算。
|
|
108
|
+
/// @author ddj 2026年09月08号
|
|
109
|
+
/// </summary>
|
|
110
|
+
private static void BeginOpen(string absPath, int line, int column)
|
|
111
|
+
{
|
|
112
|
+
string baseUrl = BaseUrl();
|
|
113
|
+
var thread = new Thread(() =>
|
|
114
|
+
{
|
|
115
|
+
try
|
|
116
|
+
{
|
|
117
|
+
if (HandoffToOpenPage(baseUrl, absPath, line, column)) return;
|
|
118
|
+
OpenUrlOnMain(BuildOpenUrl(baseUrl, absPath, line, column));
|
|
119
|
+
}
|
|
120
|
+
catch (Exception)
|
|
121
|
+
{
|
|
122
|
+
OpenUrlOnMain(BuildOpenUrl(baseUrl, absPath, line, column));
|
|
123
|
+
}
|
|
124
|
+
}) { IsBackground = true };
|
|
125
|
+
thread.Start();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// <summary>移交到已打开页面;返回 true = 已确认由页面执行。无活跃页面/2s 未领取 → false。</summary>
|
|
129
|
+
private static bool HandoffToOpenPage(string baseUrl, string absPath, int line, int column)
|
|
130
|
+
{
|
|
131
|
+
string resp = PostRpc(baseUrl, "edrv.external.handoff", HandoffArgs(absPath, line, column));
|
|
132
|
+
if (ExtractInt(resp, "clients") <= 0) return false;
|
|
133
|
+
Thread.Sleep(2000);
|
|
134
|
+
string token = ExtractString(resp, "token");
|
|
135
|
+
string state = PostRpc(baseUrl, "edrv.external.pendingState",
|
|
136
|
+
"{\"token\":" + JsonEscape(token) + ",\"take\":true}");
|
|
137
|
+
return ExtractBool(state, "delivered");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// <summary>移交载荷:行列仅在有值时附带(与 URL 深链契约一致)。</summary>
|
|
141
|
+
private static string HandoffArgs(string absPath, int line, int column)
|
|
142
|
+
{
|
|
143
|
+
var json = "{\"paths\":[" + JsonEscape(absPath) + "]";
|
|
144
|
+
if (line > 0) json += ",\"line\":" + line;
|
|
145
|
+
if (column > 0) json += ",\"column\":" + column;
|
|
146
|
+
return json + "}";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// <summary>POST /edrv/rpc;失败返回 null(调用方回退深链,不阻塞)。</summary>
|
|
150
|
+
private static string PostRpc(string baseUrl, string method, string argsJson)
|
|
151
|
+
{
|
|
152
|
+
try
|
|
153
|
+
{
|
|
154
|
+
var req = (HttpWebRequest)WebRequest.Create(baseUrl.TrimEnd('/') + "/edrv/rpc");
|
|
155
|
+
req.Method = "POST";
|
|
156
|
+
req.ContentType = "application/json";
|
|
157
|
+
req.Timeout = 4000;
|
|
158
|
+
req.Proxy = null;
|
|
159
|
+
byte[] body = Encoding.UTF8.GetBytes("{\"method\":\"" + method + "\",\"args\":" + argsJson + "}");
|
|
160
|
+
req.ContentLength = body.Length;
|
|
161
|
+
using (var stream = req.GetRequestStream()) stream.Write(body, 0, body.Length);
|
|
162
|
+
using (var res = req.GetResponse())
|
|
163
|
+
using (var reader = new StreamReader(res.GetResponseStream(), Encoding.UTF8))
|
|
164
|
+
return reader.ReadToEnd();
|
|
165
|
+
}
|
|
166
|
+
catch (Exception) { return null; }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// <summary>回退开浏览器需主线程:经 delayCall 切回。</summary>
|
|
170
|
+
private static void OpenUrlOnMain(string url)
|
|
171
|
+
{
|
|
172
|
+
EditorApplication.delayCall += () => Application.OpenURL(url);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// <summary>拼深链 URL(行列仅在有值时附带)。</summary>
|
|
176
|
+
private static string BuildOpenUrl(string baseUrl, string absPath, int line, int column)
|
|
177
|
+
{
|
|
178
|
+
var url = baseUrl.TrimEnd('/') + "/?edrvOpen=1&edrvPaths=" + Uri.EscapeDataString(absPath);
|
|
179
|
+
if (line > 0) url += "&edrvLine=" + line;
|
|
180
|
+
if (column > 0) url += "&edrvColumn=" + column;
|
|
181
|
+
return url;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// <summary>相对路径 → 绝对路径(基于 Unity 项目根补全)。</summary>
|
|
185
|
+
private static string ResolveAbsolute(string path)
|
|
186
|
+
{
|
|
187
|
+
return Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(ProjectRoot(), path));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// <summary>Unity 项目根(Assets 的父目录)。</summary>
|
|
191
|
+
private static string ProjectRoot()
|
|
192
|
+
{
|
|
193
|
+
return Directory.GetParent(Application.dataPath).FullName;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// <summary>读取 EditorPrefs 的 DSH 服务地址(空回退默认)。</summary>
|
|
197
|
+
private static string BaseUrl()
|
|
198
|
+
{
|
|
199
|
+
var stored = EditorPrefs.GetString(BaseUrlPref, DefaultBaseUrl);
|
|
200
|
+
return string.IsNullOrEmpty(stored) ? DefaultBaseUrl : stored;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// <summary>极简 JSON int 字段提取(缺失/失败 → -1)。起点 = 匹配串长度 key.Length+3("key":)。</summary>
|
|
204
|
+
private static int ExtractInt(string json, string key)
|
|
205
|
+
{
|
|
206
|
+
if (string.IsNullOrEmpty(json)) return -1;
|
|
207
|
+
int at = json.IndexOf("\"" + key + "\":", StringComparison.Ordinal);
|
|
208
|
+
if (at < 0) return -1;
|
|
209
|
+
at += key.Length + 3;
|
|
210
|
+
int end = at;
|
|
211
|
+
while (end < json.Length && (char.IsDigit(json[end]) || (end == at && json[end] == '-'))) end++;
|
|
212
|
+
int value;
|
|
213
|
+
return int.TryParse(json.Substring(at, end - at), out value) ? value : -1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// <summary>极简 JSON string 字段提取(缺失/失败 → null)。起点 = 匹配串长度 key.Length+4("key":")。</summary>
|
|
217
|
+
private static string ExtractString(string json, string key)
|
|
218
|
+
{
|
|
219
|
+
if (string.IsNullOrEmpty(json)) return null;
|
|
220
|
+
int at = json.IndexOf("\"" + key + "\":\"", StringComparison.Ordinal);
|
|
221
|
+
if (at < 0) return null;
|
|
222
|
+
at += key.Length + 4;
|
|
223
|
+
int end = json.IndexOf('"', at);
|
|
224
|
+
return end < 0 ? null : json.Substring(at, end - at);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// <summary>极简 JSON bool 字段提取(缺失 → false)。起点 = 匹配串长度 key.Length+3("key":)。</summary>
|
|
228
|
+
private static bool ExtractBool(string json, string key)
|
|
229
|
+
{
|
|
230
|
+
if (string.IsNullOrEmpty(json)) return false;
|
|
231
|
+
int at = json.IndexOf("\"" + key + "\":", StringComparison.Ordinal);
|
|
232
|
+
return at >= 0 && json.IndexOf("true", at, StringComparison.Ordinal) == at + key.Length + 3;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// <summary>JSON 字符串转义(反斜杠 + 引号)。</summary>
|
|
236
|
+
private static string JsonEscape(string value)
|
|
237
|
+
{
|
|
238
|
+
return "\"" + (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// <summary>
|
|
243
|
+
/// 注册兜底:独立类的 InitializeOnLoadMethod——主类静态构造异常导致注册缺失时补注册。
|
|
244
|
+
/// @author ddj 2026-09-08
|
|
245
|
+
/// </summary>
|
|
246
|
+
internal static class DshCodeEditorBoot
|
|
247
|
+
{
|
|
248
|
+
[InitializeOnLoadMethod]
|
|
249
|
+
private static void EnsureRegistered()
|
|
250
|
+
{
|
|
251
|
+
EditorApplication.delayCall += () =>
|
|
252
|
+
{
|
|
253
|
+
try
|
|
254
|
+
{
|
|
255
|
+
var existing = CodeEditor.Editor.GetCodeEditorForPath(DshCodeEditor.RealPath());
|
|
256
|
+
if (existing is DshCodeEditor) return;
|
|
257
|
+
CodeEditor.Register(new DshCodeEditor());
|
|
258
|
+
Debug.Log("[DshCodeEditor] 兜底注册完成(v" + DshCodeEditor.VersionText + ")");
|
|
259
|
+
}
|
|
260
|
+
catch (Exception e)
|
|
261
|
+
{
|
|
262
|
+
Debug.LogError("[DshCodeEditor] 兜底注册失败:" + e);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Dsh.Editor",
|
|
3
|
+
"references": [],
|
|
4
|
+
"includePlatforms": [
|
|
5
|
+
"Editor"
|
|
6
|
+
],
|
|
7
|
+
"excludePlatforms": [],
|
|
8
|
+
"allowUnsafeCode": false,
|
|
9
|
+
"overrideReferences": false,
|
|
10
|
+
"precompiledReferences": [],
|
|
11
|
+
"autoReferenced": true,
|
|
12
|
+
"defineConstraints": [],
|
|
13
|
+
"versionDefines": [],
|
|
14
|
+
"noEngineReferences": false
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# com.dsh.editor — DSH 文件编辑(Unity 外部脚本编辑器集成)
|
|
2
|
+
|
|
3
|
+
把 DSH 的 `dsh-vscode-mode`(VSCodeMode)编辑器注册为 Unity 外部脚本编辑器:
|
|
4
|
+
双击脚本 / Console 报错跳转时,优先把打开请求**移交已打开的 DSH 页面**就地执行(不重复开新页);
|
|
5
|
+
无活跃页面时回退经默认浏览器深链打开对应文件并定位行列。
|
|
6
|
+
|
|
7
|
+
## 安装方式一:DSH 设置页一键安装(推荐)
|
|
8
|
+
|
|
9
|
+
1. 打开 DSH Web GUI → 设置 → VSCodeMode → 通用 →「系统集成」。
|
|
10
|
+
2. 在 Unity 集成区添加你的 Unity 项目根(包含 `Assets/` 与 `ProjectSettings/` 的目录)。
|
|
11
|
+
3. 点「安装」—— 包会复制为 `<项目>/Packages/com.dsh.editor`(Unity 内嵌包,自动发现)。
|
|
12
|
+
4. 切回 Unity 窗口(或下次启动 Unity)自动生效。
|
|
13
|
+
|
|
14
|
+
后续插件更新后,同一按钮即为一键更新(整目录替换,列表里会显示「可更新」徽标)。
|
|
15
|
+
|
|
16
|
+
## 安装方式二:Package Manager 手动安装
|
|
17
|
+
|
|
18
|
+
- Unity → Window → Package Manager → 左上角 `+` → **Add package from disk** →
|
|
19
|
+
选择插件目录下的 `unity/com.dsh.editor`(含本 README 的目录)。
|
|
20
|
+
|
|
21
|
+
## 使用
|
|
22
|
+
|
|
23
|
+
1. Unity → Edit → Preferences → External Tools → External Script Editor 下拉选择 **DSH 文件编辑**。
|
|
24
|
+
2. 展开 DSH 文件编辑选项,确认「DSH 服务地址」(默认 `http://127.0.0.1:3080`,需与 DSH Web GUI 地址一致)。
|
|
25
|
+
3. 双击任意脚本即可打开(有已打开 DSH 页面时直接在其页面内打开,不再新开标签);Console 报错双击会带上行列。
|
|
26
|
+
|
|
27
|
+
## 说明
|
|
28
|
+
|
|
29
|
+
- 本包为虚拟外部编辑器:不需要本地可执行文件。打开优先走「移交」:POST 到 DSH host 的
|
|
30
|
+
`edrv.external.handoff`,已打开页面 3s 轮询领取并执行打开规则(文件夹/文件的智能路由见插件 README);
|
|
31
|
+
无活跃页面或 2s 未领取时回退 `Application.OpenURL` 深链。移交在后台线程执行,不阻塞编辑器。
|
|
32
|
+
- 编辑器内查看任意绝对路径文件;保存受 DSH 会话沙箱策略约束(工作区外保存会被拒绝,属预期安全行为)。
|
|
33
|
+
- 卸载:删除 `<项目>/Packages/com.dsh.editor` 目录,并在 External Script Editor 换回其他编辑器。
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "com.dsh.editor",
|
|
3
|
+
"displayName": "DSH 文件编辑",
|
|
4
|
+
"description": "DSH(dsh-vscode-mode)编辑器集成:把「DSH 文件编辑」注册为 Unity 外部脚本编辑器,双击脚本或 Console 报错跳转时,优先把打开请求移交已打开的 DSH 页面就地执行(不重复开新页),无活跃页面时回退经默认浏览器深链打开对应文件与行列。支持从 DSH 设置页一键安装/更新(内嵌包方式,无需手动改 manifest.json)。",
|
|
5
|
+
"version": "0.2.3",
|
|
6
|
+
"unity": "2019.4"
|
|
7
|
+
}
|