dsh-code-server-app 0.3.57 → 0.3.59
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.en.md +158 -92
- package/README.md +139 -80
- package/assets/extensions/dshcs-editor-bridge/extension.js +43 -220
- package/assets/extensions/dshcs-editor-bridge/lib/bridge-client.js +5 -37
- package/assets/extensions/dshcs-editor-bridge/package.json +2 -2
- package/lib/bridge-session.mjs +55 -5
- package/lib/bridge.mjs +11 -7
- package/lib/claim-types.js +4 -2
- package/lib/client.js +2884 -4
- package/lib/index.js +74 -206
- package/package.json +6 -29
- package/vendor/VENDOR.json +1 -1
- package/assets/extensions/dshcs-editor-bridge/lib/ask-panel.js +0 -416
- package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +0 -22
- package/assets/extensions/dshcs-editor-bridge/webview/src/app.jsx +0 -242
- package/assets/extensions/dshcs-editor-bridge/webview/src/approval.jsx +0 -61
- package/assets/extensions/dshcs-editor-bridge/webview/src/official-tokens.css +0 -576
- package/assets/extensions/dshcs-editor-bridge/webview/src/panel.css +0 -380
- package/assets/extensions/dshcs-editor-bridge/webview/src/thread.jsx +0 -160
- package/assets/extensions/dshcs-editor-bridge/webview/thread.css +0 -1
- package/assets/extensions/dshcs-editor-bridge/webview/thread.js +0 -465
package/lib/client.js
CHANGED
|
@@ -1,6 +1,2886 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/client.js —— DSH 客户端半部(**手写源码,不是构建产物;0.3.58 起不再构建**)
|
|
3
|
+
*
|
|
4
|
+
* 为什么不构建:这个文件必须是一个**经典脚本**(DSH 用 <script src> 加载,不是 ES module),
|
|
5
|
+
* 且只有两种形态可用 —— 外部依赖走 factory 的 require('react') / require('react/jsx-runtime'),
|
|
6
|
+
* 包内分块只能 require.async('client.*.js')。既然产物只能是单文件,就把它当成源码维护:
|
|
7
|
+
* 改这里就是改客户端,没有中间产物、没有构建步骤、也没有"忘了重建"的漂移。
|
|
8
|
+
*
|
|
9
|
+
* 硬性约束(改动前先读;有 scripts/test-client-entry.mjs 守着):
|
|
10
|
+
* ① 顶层不许出现 import / export / await —— 经典脚本里它们是语法错误;
|
|
11
|
+
* ② require(...) 的实参只允许 'react' 与 'react/jsx-runtime'(DSH 冻结模块表);
|
|
12
|
+
* ③ 必须保留 window.__ModuleLoader__.load({id, factory}) 这层包装与 module.exports 的
|
|
13
|
+
* { apply, inject, name } 形状(app/inject/name 的定义在文件末尾的"插件主体"段);
|
|
14
|
+
* ④ 各段落原来的顶层标识符现在共享同一作用域 —— 新增顶层名字时先搜一遍别撞名
|
|
15
|
+
* (var/function 撞名是**静默覆盖**,不会报错)。
|
|
16
|
+
*
|
|
17
|
+
* 段落(按依赖顺序):地址语法 → 工作区解析 → 常驻面 → 全屏动作 → 认领类型(与 lib/claim-types.js
|
|
18
|
+
* 同源的一致性副本)→ 插件主体。
|
|
19
|
+
*/
|
|
20
|
+
|
|
1
21
|
window.__ModuleLoader__.load({id:'dsh-code-server-app',factory:function(require){var module={exports:{}};var exports=module.exports;
|
|
2
|
-
var Z=Object.defineProperty;var We=Object.getOwnPropertyDescriptor;var Ye=Object.getOwnPropertyNames;var Ge=Object.prototype.hasOwnProperty;var Xe=(e,r)=>{for(var n in r)Z(e,n,{get:r[n],enumerable:!0})},Qe=(e,r,n,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let a of Ye(r))!Ge.call(e,a)&&a!==n&&Z(e,a,{get:()=>r[a],enumerable:!(t=We(r,a))||t.enumerable});return e};var Je=e=>Qe(Z({},"__esModule",{value:!0}),e);var Jr={};Xe(Jr,{apply:()=>Yr,inject:()=>Xr,name:()=>Qr});module.exports=Je(Jr);var fe="dsh-resource://file/",Ve="sidebar://";function K(e){return typeof e=="string"&&e.startsWith(Ve)}function F(e){if(typeof e!="string"||!e.startsWith(fe))return null;for(var r=e.length,n=0;n<e.length;n+=1){var t=e.charAt(n);if(t==="?"||t==="#"){r=n;break}}var a=e.slice(fe.length,r).split("/"),i=a.shift();try{if(i==="session"){var o=a.shift();return o===void 0||o===""||a.length===0?null:{scope:"session",sessionId:decodeURIComponent(o),path:pe(a)}}if(i==="absolute"){var c=a[0]===""&&a.length>1,p=c?a.slice(1):a,f=pe(p);return(f===""||f.charAt(0)==="/")&&!c?null:c?{scope:"absolute",path:"//"+f}:f===""?null:{scope:"absolute",path:ve(f)?f:"/"+f}}}catch(w){return null}return null}function pe(e){for(var r=[],n=0;n<e.length;n+=1)r.push(decodeURIComponent(e[n]));return r.join("/")}function ve(e){return/^[A-Za-z]:($|\/)/.test(e)}function Ze(e){return typeof e!="string"||e===""?!1:e.charAt(0)==="/"?!0:ve(e)}function me(e){var r=F(e);if(r===null)return"";var n=r.path.replace(/[/\\]+$/,""),t=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\")),a=t===-1?n:n.slice(t+1);return a}function he(e,r){if(e===null||e==null)return null;var n=typeof e.path=="string"?e.path:"";if(n==="")return null;if(Ze(n))return n;if(typeof r!="string"||r==="")return null;var t=r.indexOf("\\")!==-1?"\\":"/",a=r.replace(/[\\/]+$/,"");return a+t+(t==="\\"?n.replace(/\//g,"\\"):n)}var q=["md","markdown","html","htm","png","jpg","jpeg","gif","webp","bmp","ico","svg","pdf"],B=["exe","com","msi","msix","msixbundle","appx","appxbundle","dll","sys","scr","cpl","ocx","drv","efi","mui","obj","o","a","lib","pdb","class","jar","pyc","pyo","wasm","node","so","dylib","ko","elf","bin","out","apk","ipa","deb","rpm","dmg","iso","img","cab"],W=["doc","docx","docm","dot","dotx","dotm","docb","rtf","odt","xls","xlsx","xlsm","xlsb","xlt","xltx","xltm","xla","xlam","ods","ppt","pptx","pptm","pot","potx","potm","pps","ppsx","ppam","odp","vsd","vsdx","vssx","vstx","vsdm","vssm","vstm","one","onetoc2","mpt","mpp","pub","msg","xps","oxps","odg"],$=["*"].concat(q).concat(B).concat(W).map(function(e,r){return r===0?e:"!"+e}).join(";"),Ke=/[;,\s]+/;function qe(e){return String(e).replace(/!\s+/g,"!").split(Ke)}function $e(e){var r=typeof e=="string"?e.trim():"";if(r==="")return"";var n=r.charAt(0)==="!",t=n?r.slice(1).trim():r;return t==="*"?n?"!*":"*":(t.slice(0,2)==="*."?t=t.slice(2):t.charAt(0)==="."&&(t=t.slice(1)),t=t.toLowerCase(),t===""?"":n?"!"+t:t)}function A(e){if(typeof e!="string")return"";for(var r=qe(e),n=[],t=0;t<r.length;t+=1){var a=$e(r[t]);a===""||a==="!*"||n.indexOf(a)===-1&&n.push(a)}return n.join(";")}function Y(e){for(var r=A(e),n=r===""?[]:r.split(";"),t=!1,a=[],i=[],o=0;o<n.length;o+=1){var c=n[o];if(c==="*"){t=!0;continue}c.charAt(0)==="!"?i.push(c.slice(1)):a.push(c)}return{all:t,allow:a,deny:i}}function er(e){if(typeof e!="string"||e==="")return"";var r=e.replace(/[/\\]+$/,""),n=Math.max(r.lastIndexOf("/"),r.lastIndexOf("\\"));n!==-1&&(r=r.slice(n+1));var t=r.lastIndexOf(".");return t<=0?"":r.slice(t+1).toLowerCase()}function rr(e,r){if(r==null)return!1;var n=er(e);return n!==""&&r.deny.indexOf(n)!==-1?!1:n!==""&&r.allow.indexOf(n)!==-1?!0:r.all===!0}function be(e,r){return e===null||e==null?!1:rr(e.path,r)}function ye(e){var r=Y(e),n=[];if(r.all&&n.push("\u5176\u4F59\u7C7B\u578B\u5168\u90E8\u8BA4\u9886"),r.allow.length>0&&n.push("\u6307\u5B9A\u8BA4\u9886 "+r.allow.length+" \u9879"),r.deny.length>0){var t=[];ge(r.deny,B)&&t.push("\u53EF\u6267\u884C\u6587\u4EF6"),ge(r.deny,W)&&t.push("Office \u6587\u6863"),n.push("\u6392\u9664 "+r.deny.length+" \u9879"+(t.length>0?"(\u542B"+t.join("\u3001")+")":""))}return n.length===0?"\u4E0D\u8BA4\u9886\u4EFB\u4F55\u6587\u4EF6(\u53EA\u6709\u9875\u9762\u6807\u7B7E)":n.join(" \xB7 ")}function ge(e,r){for(var n=new Set(r),t=0;t<e.length;t+=1)if(n.has(e[t]))return!0;return!1}var nr="[data-sidebar-right-panel]",tr='[data-sidebar-right-mode="fullscreen"]';function xe(e){if(e==null||typeof e.closest!="function")return"no-root";var r,n;try{if(r=e.closest(nr),r==null||typeof r.querySelector!="function")return"no-panel";n=r.querySelector(tr)}catch(t){return"lookup-failed"}if(n==null||typeof n.click!="function")return"no-control";try{n.click()}catch(t){return"click-failed"}return"clicked"}function we(e,r){if(!(e==null||e.byId==null)&&!(typeof r!="string"||r==="")){var n=e.byId[r];if(n!=null)return typeof n.cwd=="string"&&n.cwd!==""?n.cwd:void 0}}function ee(e,r){if(!(e==null||!Array.isArray(e.items))&&!(typeof r!="string"||r===""))for(var n=0;n<e.items.length;n+=1){var t=e.items[n];if(!(t==null||!Array.isArray(t.sessionIds))&&t.sessionIds.indexOf(r)!==-1&&typeof t.path=="string"&&t.path!=="")return t.path}}function re(e){var r=e==null?{}:e,n=r.sessions==null?null:r.sessions,t=r.workspaces==null?null:r.workspaces,a=typeof r.sessionId=="string"&&r.sessionId!==""?r.sessionId:void 0,i=we(n,a);if(i!==void 0)return i;var o=n==null?void 0:n.current,c=we(n,o);if(c!==void 0)return c;var p=ee(t,a);if(p!==void 0)return p;var f=ee(t,typeof o=="string"?o:void 0);if(f!==void 0)return f;var w=n!=null&&Array.isArray(n.ids)?n.ids[0]:void 0,v=ee(t,typeof w=="string"?w:void 0);if(v!==void 0)return v;if(t!=null&&Array.isArray(t.items)&&t.recentWorkspaceId!==void 0)for(var E=0;E<t.items.length;E+=1){var d=t.items[E];if(!(d==null||d.workspaceId!==t.recentWorkspaceId)&&typeof d.path=="string"&&d.path!=="")return d.path}if(t!=null&&Array.isArray(t.items)&&t.items.length>0){var m=t.items[0];if(m!=null&&typeof m.path=="string"&&m.path!=="")return m.path}}var j=typeof Element!="undefined"&&typeof Element.prototype.moveBefore=="function",ar=-2e4,l={park:null,frame:null,currentSrc:null,sameOrigin:null,owner:null,lastRect:null,parkStrategy:"offscreen",degraded:!1,preloaded:!1,nudgeCount:0,nudgeEnabled:!0,lastNudgeAt:0,listeners:new Set};function ne(){return{ready:l.frame!==null,docked:l.owner!==null,owner:l.owner,src:l.currentSrc,sameOrigin:l.sameOrigin,parkStrategy:l.parkStrategy,degraded:l.degraded,preloaded:l.preloaded,supportsMoveBefore:j,nudgeCount:l.nudgeCount,lastNudgeAt:l.lastNudgeAt}}function D(){var e=ne();l.listeners.forEach(function(r){try{r(e)}catch(n){}})}function Ee(e){return l.listeners.add(e),function(){l.listeners.delete(e)}}function ke(){return ne()}function sr(e){e!=="offscreen"&&e!=="behind"||l.parkStrategy!==e&&(l.parkStrategy=e,l.frame!==null&&l.owner===null&&te(),D())}function lr(){return l.parkStrategy}function ir(){var e=l.lastRect;e!==null&&(l.park.style.width=Math.max(1,Math.round(e.width))+"px",l.park.style.height=Math.max(1,Math.round(e.height))+"px")}function te(){if(l.park!==null){if(ir(),l.parkStrategy==="behind"&&l.lastRect!==null){l.park.style.left=Math.round(l.lastRect.left)+"px",l.park.style.top=Math.round(l.lastRect.top)+"px",l.park.style.visibility="visible",l.park.style.zIndex="0";return}l.park.style.left=ar+"px",l.park.style.top="0px",l.park.style.visibility="hidden",l.park.style.zIndex="0"}}function Se(){l.frame!==null&&(l.frame.setAttribute("inert",""),l.frame.setAttribute("aria-hidden","true"))}function or(){l.frame!==null&&(l.frame.removeAttribute("inert"),l.frame.removeAttribute("aria-hidden"))}function cr(){var e=document.createElement("div");e.className="dshcs-park",e.setAttribute("data-dshcs-park",""),e.setAttribute("aria-hidden","true");var r=document.createElement("iframe");return r.className="dshcs-frame",r.setAttribute("title","code-server"),r.setAttribute("allow","clipboard-read; clipboard-write"),r.setAttribute("data-dshcs-resident",""),r.src="about:blank",e.appendChild(r),document.body.appendChild(e),l.park=e,l.frame=r,l.currentSrc="about:blank",l.owner=null,te(),Se(),r}function dr(e){var r=l.frame;if(r!==null){var n=l.sameOrigin!==e;!n&&r.hasAttribute("data-dshcs-attrs")||(e===!0?r.removeAttribute("sandbox"):e===!1&&r.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-pointer-lock allow-clipboard-read allow-clipboard-write"),r.setAttribute("data-dshcs-attrs",e===!0?"same-origin":"cross-origin"),l.sameOrigin=e)}}function ae(e){var r=e||{};return l.frame===null&&cr(),typeof r.sameOrigin=="boolean"&&dr(r.sameOrigin),typeof r.src=="string"&&r.src!==""&&r.src!==l.currentSrc&&ur(r.src),l.frame}function ur(e){l.frame===null||typeof e!="string"||e===""||e===l.currentSrc||(l.currentSrc=e,l.frame.src=e,D())}function Ce(){var e=l.frame;if(l.nudgeEnabled!==!0||e===null||e.isConnected!==!0)return!1;var r=e.style.display;return e.style.display="none",e.offsetHeight,e.style.display=r,l.nudgeCount+=1,l.lastNudgeAt=Date.now(),!0}function fr(e){return l.nudgeEnabled=e!==!1,l.nudgeEnabled}function Ne(e){if(l.frame!==null){if(j)try{e.moveBefore(l.frame,null);return}catch(r){l.degraded=!0,l.lastMoveError=(r&&r.name?r.name+": ":"")+(r&&r.message?r.message:String(r))}else l.degraded=!0;e.appendChild(l.frame)}}function se(e,r){if(!(l.frame===null||e==null)){var n=l.owner===null,t=e.getBoundingClientRect();t.width>=1&&t.height>=1&&(l.lastRect={left:t.left,top:t.top,width:t.width,height:t.height}),l.owner=r===void 0?null:r,or(),l.park.setAttribute("aria-hidden","true"),Ne(e),n&&Ce(),D()}}function z(e){l.frame!==null&&(e!==void 0&&l.owner!==e||(l.owner=null,te(),Se(),l.park.setAttribute("aria-hidden","true"),Ne(l.park),D()))}function le(e){var r=e||{};return ae(r),l.owner===null&&(typeof r.src=="string"&&r.src!==""&&(l.preloaded=!0),z()),l.frame}function pr(){l.park!==null&&l.park.parentNode!==null&&l.park.parentNode.removeChild(l.park),l.park=null,l.frame=null,l.currentSrc=null,l.sameOrigin=null,l.owner=null,l.lastRect=null,l.degraded=!1,l.preloaded=!1,l.nudgeCount=0,l.lastNudgeAt=0,D()}typeof window!="undefined"&&(window.__dshcsSurface={snapshot:ne,setParkStrategy:sr,getParkStrategy:lr,preload:le,dock:se,park:z,nudge:Ce,setNudgeEnabled:fr,destroy:pr});var s=require("react"),ce=new Set,P={status:null,busy:!1,sidebarUi:"unknown",sidebarRegisterFailed:!1};function L(e){P=Object.assign({},P,e),ce.forEach(function(r){r()})}function vr(e){return ce.add(e),function(){ce.delete(e)}}function mr(){return P}function ue(){return s.useSyncExternalStore(vr,mr)}var hr="/api",gr="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMTQ3IDE0NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8c3R5bGU+QG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgeyogeyBmaWxsOiB3aGl0ZTsgfX08L3N0eWxlPgogIDxwYXRoIGQ9Im00Mi40MjE0LDM5LjY1NWMtMjQuNDA1NywwIC00Mi4xNTU0LDEzLjE3MjEgLTQyLjE1NTQsMzMuODQ1YzAsMjAuNTgxNCAxOC40ODkyLDMzLjg0NSA0Mi4xNTU0LDMzLjg0NWMyMy42NjYyLDAgMzguMTgwMywtMTEuNjE3MSAzOC43MzQ5LC0yOC43MjI1bC0yMS4wNzc3LC0wLjQ1NzRjLTAuOTI0NCw5LjMzMDMgLTkuMTA1OSwxNS4xODQ1IC0xNy42NTcyLDE1LjE4NDVjLTExLjc0MDYsMCAtMjAuNDMwNiwtNy41OTIyIC0yMC40MzA2LC0xOS44NDk2YzAsLTEyLjI1NzQgOC42OSwtMTkuOTg2OCAyMC40MzA2LC0yMC4yMTU1YzguNTUxMywtMC4xODMgMTYuOTE3Nyw1Ljk0NTcgMTcuNDcyMywxNS4yNzZsMjEuMDc3NywtMC42NDAzYy0wLjQ2MjIsLTE2LjgzMTEgLTE0LjE0NDIsLTI4LjI2NTIgLTM4LjU1LC0yOC4yNjUyem00OC44NDQ2LDJsNTUuNDY4LDBsMCw2NC4wMzExbC01NS40NjgsMGwwLC02NC4wMzExeiIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg==";async function x(e,r){var n={method:r===void 0?"GET":"POST",headers:{}};r!==void 0&&(n.headers["content-type"]="application/json",n.body=JSON.stringify(r));var t=await fetch(hr+e,n),a=await t.text(),i=null;try{i=a===""?null:JSON.parse(a)}catch(o){i=null}return t.ok?i!==null?i:{ok:!1,error:"invalid JSON response"}:{ok:!1,error:"HTTP "+t.status+(i!==null&&i.error!==void 0?": "+i.error:"")}}function Oe(){}function De(e){var r=typeof e.useSessions=="function"?e.useSessions:Oe,n=typeof e.useWorkspaces=="function"?e.useWorkspaces:Oe;return{sessions:r(function(t){return t}),workspaces:n(function(t){return t})}}function ze(e,r){if(e==null||typeof e.url!="string"||e.url==="")return null;var n=e.pid!=null?String(e.pid):e.startedAt!=null?String(e.startedAt):"0",t=e.url+"?s="+encodeURIComponent(n),a=typeof r=="string"&&r!==""?r:e!=null&&e.cwd!=null?e.cwd:null;if(a==null||a==="")return t;var i=a.replace(/\\/g,"/"),o=i;return(/^[A-Za-z]:\//.test(i)||i.charCodeAt(0)!==47)&&(o="/"+i),t+"&folder="+encodeURIComponent(o)}function br(e){var r=e.status,n=e.pageUrl,t=e.reloadTick,a=e.owner,i=s.useRef(null),o=r!=null&&r.ok===!0&&r.running===!0,c=r!=null&&r.status==="starting",p=r!=null&&r.ok===!1,f=r!=null&&r.serve==="dsh";if(s.useLayoutEffect(function(){if(!o||n===null){z(a);return}ae({src:n,sameOrigin:f}),i.current!==null&&se(i.current,a)},[o,n,f,t,a]),s.useLayoutEffect(function(){return function(){z(a)}},[a]),o&&n!==null)return s.createElement("div",{ref:i,className:"dshcs-slot","data-code-server-slot":a!=null?a:"code-server"});if(c)return s.createElement("div",{className:"dshcs-loading"},"\u6B63\u5728\u542F\u52A8 code-server\u2026");var w=p&&r!=null&&r.error?r.error:"code-server \u672A\u8FD0\u884C",v=f?"\u5F53\u524D\u4EE5 DSH \u540C\u6E90\u8DEF\u5F84 "+(r!=null&&r.url||"/code-server/")+" \u63D0\u4F9B(\u65E0\u72EC\u7ACB\u7AEF\u53E3)\u3002":"\u5F53\u524D\u4EE5\u72EC\u7ACB\u56DE\u73AF\u7AEF\u53E3\u63D0\u4F9B(\u7AEF\u53E3 "+(r!=null&&r.port!=null?r.port:"\u968F\u673A")+" + \u8DEF\u5F84\u4EE4\u724C;\u8981\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00,\u590D\u5236\u8BBE\u7F6E\u5361\u91CC\u7684\u5B8C\u6574\u5730\u5740 \u2014\u2014 \u7F3A\u4E86\u4EE4\u724C\u90A3\u6BB5\u4F1A 404)\u3002",E=j?"\u5E38\u9A7B\u9762:\u53EF\u7528(\u5207\u6807\u7B7E/\u6536\u8D77\u4FA7\u680F\u4E0D\u91CD\u8F7D)\u3002":"\u5E38\u9A7B\u9762:\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301 Element.moveBefore \u2014\u2014 \u5207\u6807\u7B7E\u4F1A\u6574\u9875\u91CD\u8F7D(\u5347\u7EA7\u6D4F\u89C8\u5668\u540E\u81EA\u52A8\u53EF\u7528)\u3002";return s.createElement("div",{className:"dshcs-empty"},s.createElement("div",{className:"dshcs-emptybox"},s.createElement("div",null,p?"code-server \u542F\u52A8\u5931\u8D25":"code-server \u672A\u8FD0\u884C"),s.createElement("pre",{className:"dshcs-error"},w),s.createElement("p",{className:"dshcs-hint"},"VS Code \u6811\u968F\u63D2\u4EF6\u5305\u5185\u7F6E\u3001\u5C31\u5730\u8FD0\u884C(\u65E0\u9700\u5168\u5C40\u5B89\u88C5\u3001\u65E0\u9700\u8054\u7F51\u5B89\u88C5\u3001\u65E0\u300C\u5B89\u88C5\u73AF\u5883\u300D\u6B65\u9AA4);\u5185\u90E8\u4F9D\u8D56\u4E0E\u9884\u7F16\u8BD1\u539F\u751F\u6A21\u5757\u7531\u5305\u7BA1\u7406\u5668\u5728\u5B89\u88C5\u63D2\u4EF6\u65F6\u4E00\u5E76\u88C5\u597D(\u65E0\u9700 C++ \u5DE5\u5177\u94FE)\u3002\u82E5\u6B64\u5904\u957F\u671F\u672A\u8FD0\u884C,\u8BF7\u67E5\u770B DSH host \u65E5\u5FD7\u91CC\u7684 [code-server] \u8F93\u51FA(\u6216\u5728\u8BBE\u7F6E\u5361\u7247\u91CC\u5207\u6362\u300C\u540E\u53F0\u5E38\u9A7B\u300D\u89E6\u53D1\u4E00\u6B21\u542F\u52A8)\u3002"+v+E)))}function yr(e){var r=ue(),n=r.status,t=n!=null&&n.ok===!0&&n.running===!0,a=n!=null&&n.keepResident===!0,i=De(e!=null?e:{}),o=re({sessions:i.sessions,workspaces:i.workspaces}),c=t?ze(n,o):null;return s.useEffect(function(){!a||!t||c===null||le({src:c,sameOrigin:n!=null&&n.serve==="dsh"})},[a,t,c,n!=null?n.serve:null]),null}var xr=".dshcs-frame{display:block;position:absolute;inset:0;z-index:1;width:100%;height:100%;border:0;background:var(--dsw-alias-bg-base,#fff)}.dshcs-tabroot{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;background:var(--dsw-alias-bg-base,#fff)}.dshcs-slot{position:relative;display:block;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;background:var(--dsw-alias-bg-base,#fff)}.dshcs-park{position:fixed;left:-20000px;top:0;width:320px;height:200px;overflow:hidden;pointer-events:none;z-index:0;visibility:hidden;contain:strict}.dshcs-loading{position:absolute;inset:0;z-index:2;display:grid;place-items:center;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-tertiary,#7d8798);font-size:13px}.dshcs-empty{position:relative;z-index:2;flex:1;display:flex;align-items:center;justify-content:center;padding:44px 24px 24px}.dshcs-emptybox{max-width:560px;text-align:left;background:var(--dsw-alias-bg-layer-2,#f7f8fb);border:1px solid var(--dsw-alias-border-l2,#dfe3eb);border-radius:12px;padding:16px 18px}.dshcs-error{color:var(--dsw-alias-label-error,#d92d20);white-space:pre-wrap;word-break:break-all;font-family:ui-monospace,Consolas,monospace;font-size:12px;margin:8px 0 0}.dshcs-hint{color:var(--dsw-alias-label-tertiary,#7d8798);font-size:12px;margin:6px 0 0}.dshcs-legacy{color:var(--dsw-alias-label-warning,#b54708);font-size:12px;font-weight:600;margin:0 0 6px}",Le="dsh-code-server/styles";typeof document!="undefined"&&document.querySelector("style[data-dshcs="+JSON.stringify(Le)+"]")===null&&(G=document.createElement("style"),G.setAttribute("data-dshcs",Le),G.textContent=xr,document.head.appendChild(G));var G,Te="code-server",Ae="dsh-code-server-app",wr="dsh-code-server-app",Er="\u5728\u53F3\u4FA7\u680F\u6807\u7B7E\u91CC\u8FD0\u884C VS Code \u7F51\u9875\u7248,\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A;\u8BA4\u9886\u7C7B\u578B\u3001\u6253\u5F00\u5373\u5168\u5C4F\u3001\u540E\u53F0\u5E38\u9A7B\u90FD\u5728\u8FD9\u91CC\u8BBE\u3002",kr=2500,Sr=1e4,Cr=1500;function Nr(e){var r=e!=null&&typeof e.size=="number"?e.size:16;return s.createElement("img",{src:gr,alt:"","aria-hidden":!0,draggable:!1,className:e!=null?e.className:void 0,style:{width:r,height:r,display:"block",objectFit:"contain",WebkitUserDrag:"none",userSelect:"none"}})}var Or=Y("");function Lr(){var e=P.status;return e==null||typeof e.claimExtensions!="string"?Or:Y(e.claimExtensions)}function Tr(){for(var e=["*"],r=[q,B,W],n=0;n<r.length;n+=1){for(var t=[],a=0;a<r[n].length;a+=1)t.push("!"+r[n][a]);e.push(t.join(";"))}return e.join(`
|
|
3
|
-
`)}var X=new Map;function Ar(e,r){var n=0;return X.forEach(function(t,a){if(a!==e&&!(r!==null&&t.pane!==null&&t.pane!==r))try{t.close(),X.delete(a),n+=1}catch(i){console.warn("[code-server] \u6536\u8D77\u65E7\u7684 code-server \u6807\u7B7E\u9875\u5931\u8D25:"+(i!=null&&i.message!=null?i.message:i))}}),n}function Ir(e){var r=e.useTabInfo(),n=r.tab,t=ue(),a=t.status,i=n.navigation,o=i!=null&&typeof i.revision=="number"?i.revision:0,c=i!=null&&typeof i.address=="string"?i.address:"",p=K(c)?null:F(c),f=De(e),w=p!=null&&typeof p.sessionId=="string"?p.sessionId:void 0,v=re({sessions:f.sessions,workspaces:f.workspaces,sessionId:w!==void 0?w:e.sessionId}),E=s.useRef(!1),d=he(p,v),m=i!=null&&i.params!=null&&typeof i.params.line=="number"?i.params.line:null,h=s.useRef(void 0),[k,V]=s.useState(0),_=s.useRef(null),S=s.useRef(!1),I=r.tab!=null&&r.tab.visible===!0,U=r.sidebar!=null&&r.sidebar.fullscreen===!0,T=r.panel!=null&&r.panel.id!==void 0?r.panel.id:null,y=n.actions!=null?n.actions:null,C=n.id,M=s.useRef(!1);s.useLayoutEffect(function(){if(y!==null)return X.set(C,{pane:T,close:y.close}),function(){X.delete(C)}},[C,T,y]),s.useLayoutEffect(function(){M.current||I!==!0||y===null||(M.current=!0,Ar(C,T))},[I,C,T,y]);var g=a!=null&&a.fullscreenOnOpen!==!1;return s.useLayoutEffect(function(){if(!I){S.current=!1;return}if(!(!g||U||S.current)){S.current=!0;var b=xe(_.current);b!=="clicked"&&console.warn("[code-server] \u672A\u80FD\u81EA\u52A8\u5207\u5230\u5168\u5C4F\u53F3\u4FA7\u680F("+b+");\u672C\u6B21\u6253\u5F00\u4FDD\u6301\u539F\u6A21\u5F0F")}},[I,U,g]),s.useEffect(function(){if(!(typeof v!="string"||v==="")&&h.current!==v){var b=h.current!==void 0;h.current=v;var N=!1;return x("/code-server/start",{cwd:v}).then(function(H){N||(L({status:H}),b&&H!=null&&H.ok===!0&&H.running===!0&&V(function(Be){return Be+1}))}).catch(function(){}),function(){N=!0}}},[v]),s.useEffect(function(){typeof v=="string"&&v!==""||E.current||(E.current=!0,console.warn("[code-server] \u672A\u80FD\u89E3\u6790\u5F53\u524D\u5DE5\u4F5C\u533A\u76EE\u5F55(\u4F1A\u8BDD\u65E0 cwd / \u6807\u51C6 prop \u65E0 sessionId / \u5DE5\u4F5C\u533A\u8868\u4E5F\u6CA1\u5339\u914D\u4E0A):\u672C\u6B21\u4E0D\u5411\u5BBF\u4E3B\u53D1\u9001 cwd,IDE \u4F1A\u4EE5\u7A7A\u5DE5\u4F5C\u533A\u6253\u5F00"))},[v]),s.useEffect(function(){x("/code-server/status").then(function(N){N!=null&&L({status:N})}).catch(function(){});var b=window.setInterval(function(){x("/code-server/status").then(function(N){L({status:N})}).catch(function(){})},3e3);return function(){window.clearInterval(b)}},[]),s.useEffect(function(){d!==null&&x("/code-server/open-file",m===null?{file:d}:{file:d,line:m}).catch(function(){})},[o,d,m]),s.createElement("div",{className:"dshcs-tabroot","data-code-server-tab":"body",ref:_},s.createElement(br,{status:a,pageUrl:ze(a,v),reloadTick:k,owner:"tab:"+n.id}))}function ie(e,r){if(e==null)return null;var n=e[r];if(n!=null)return n;if(typeof e.get=="function"){var t=e.get(r);if(t!=null)return t}return null}function Mr(e){var r=ie(e,"sidebarRightTabs"),n=ie(e,"sidebarRight");if(r==null||n==null)return console.error("[code-server] \u53F3\u4FA7\u680F\u670D\u52A1\u4E0A\u4E0B\u6587\u4E0D\u53EF\u89C1(sidebarRightTabs="+(r!=null)+", sidebarRight="+(n!=null)+"):\u8DF3\u8FC7\u4FA7\u680F\u6CE8\u518C"),!1;var t=ie(e,"slots");if(e.effect(function(){return r.register({id:Ae,kind:Te,priority:"extension",patterns:["dsh-resource://file/**"],canOpen:function(a){return be(F(a),Lr())},title:function(a){if(K(a)||a===void 0||a===null||a==="")return"Code Server";var i=me(a);return i===""?"Code Server":i},guide:[{order:20,title:function(){return"Code Server"},description:function(){return"\u5728\u53F3\u4FA7\u680F\u6807\u7B7E\u91CC\u8FD0\u884C VS Code \u7F51\u9875\u7248,\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A;\u4EA7\u7269/\u4EA4\u4ED8\u6587\u4EF6\u70B9\u51FB\u540E\u5728\u6B64\u6253\u5F00\u3002"},icon:Nr}]})},"code-server: sidebar tab type"),t!=null)e.effect(function(){return t.inject("sidebar.right.pane.tab",function(){return t.register({name:"sidebar.right.pane.tab",key:Ae},Ir)})},"code-server: sidebar tab body");else return console.error("[code-server] slots \u670D\u52A1\u4E0D\u53EF\u89C1:\u4FA7\u680F\u6807\u7B7E body \u672A\u6CE8\u518C"),!1;return e.effect(function(){return function(){L({sidebarUi:"unknown"})}},"code-server: sidebar mode reset"),console.log("[code-server] right-sidebar tab registered (kind="+Te+")"),!0}var Rr=".dshcs-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.dshcs-card:hover{border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}.dshcs-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}.dshcs-cardHeader{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.dshcs-cardHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.dshcs-cardHeadText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.dshcs-cardName{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.dshcs-cardDescription{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:13px;line-height:1.5}.dshcs-cardChevron{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));flex:none;transition:transform .16s;display:inline-flex}.dshcs-cardChevronOpen{transform:rotate(180deg)}.dshcs-cardBody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.dshcs-cardReadOnly{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:12px 0 0;font-size:12px;line-height:1.5}.dshcs-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.dshcs-cardFooter{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.dshcs-cardFailed{min-width:0;color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));flex:1;margin:0;font-size:12px;line-height:1.5}.dshcs-cbtn{appearance:none;font:inherit;cursor:pointer;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;border:1px solid transparent;transition:color .12s ease,background .12s ease}.dshcs-cbtn:disabled{opacity:.4;cursor:default}.dshcs-cbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.dshcs-cbtnOutline{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.dshcs-cbtnOutline:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,transparent)}.dshcs-cbtnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-primary);font-weight:600}.dshcs-cbtnPrimary:hover:not(:disabled){filter:brightness(.95)}.dshcs-cbtnPrimary:active:not(:disabled){filter:brightness(.9)}.dshcs-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.dshcs-field+.dshcs-field{border-top:1px solid var(--dsw-alias-border-l2)}.dshcs-fieldHead{align-items:center;gap:8px;display:flex}.dshcs-fieldLabel{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.dshcs-card .dshcs-hint{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;line-height:1.5}.dshcs-check{display:flex;align-items:center;gap:8px;cursor:pointer}.dshcs-check input{accent-color:var(--dsw-alias-brand-primary);width:15px;height:15px;margin:0;flex:none}.dshcs-check input:disabled{cursor:default}.dshcs-text{display:flex}.dshcs-text input,.dshcs-text textarea{font:12px/1.5 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3,transparent);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 8px;width:100%;min-width:0}.dshcs-text textarea{display:block;box-sizing:border-box;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.dshcs-text input:disabled,.dshcs-text textarea:disabled{opacity:.5;cursor:default}.dshcs-text input:focus-visible,.dshcs-text textarea:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}.dshcs-code{display:block;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font:11.5px/1.55 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2,transparent);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 8px;margin:4px 0 0;max-width:100%}.dshcs-badges{align-items:center;gap:8px;display:inline-flex}.dshcs-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.dshcs-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.dshcs-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.dshcs-reset:disabled{cursor:default}.dshcs-cfgpage{display:flex;flex-direction:column;gap:0}.dshcs-cfgfoot{justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}",jr="dsh-code-server/Card.module.css";function Dr(e,r){if(typeof document!="undefined"&&document.querySelector("style[data-plugin-css="+JSON.stringify(e)+"]")===null){var n=document.createElement("style");n.dataset.plugin="dsh-code-server",n.dataset.pluginCss=e,n.textContent=r,document.head.appendChild(n)}}Dr(jr,Rr);function zr(e){return s.createElement("svg",{width:14,height:14,viewBox:"0 0 14 14",fill:"none","aria-hidden":!0,className:e.className},s.createElement("path",{d:"M3.5 5.25L7 8.75L10.5 5.25",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"}))}function Q(e){return s.createElement("button",{type:"button",className:"dshcs-cbtn "+(e.variant==="primary"?"dshcs-cbtnPrimary":"dshcs-cbtnOutline"),disabled:e.disabled===!0,onClick:e.onClick},e.children)}function Ie(e){return s.createElement("label",{className:"dshcs-check"},s.createElement("input",{type:"checkbox",checked:e.checked===!0,disabled:e.disabled===!0,onChange:function(r){e.onChange(r.target.checked)}}),s.createElement("span",{className:"dshcs-hint"},e.children))}function Pr(e){var r=typeof e.value=="string"?e.value:"",n=Math.min(12,Math.max(3,Math.ceil(r.length/64)));return s.createElement("label",{className:"dshcs-text"},s.createElement("textarea",{value:e.value,disabled:e.disabled===!0,rows:n,spellCheck:!1,autoComplete:"off",wrap:"soft",placeholder:e.placeholder,onChange:function(t){e.onChange(t.target.value)}}))}function oe(e){var r=[];return e.overridden===!0&&(r.push(s.createElement("span",{className:"dshcs-badge"},e.overriddenLabel)),r.push(s.createElement("button",{type:"button",className:"dshcs-reset",disabled:e.disabled===!0,onClick:e.onReset},e.resetLabel))),r.length===0?null:s.createElement("span",{className:"dshcs-badges"},...r)}function Me(e){var[r,n]=s.useState(e.defaultOpen===!0),t=e.state;if(t==null||t.available!==!0)return null;var a=e.title,i=e.noticeOnly===!0,o=t.dirty!==!0||t.invalid===!0||t.saving===!0;return s.createElement("li",{className:"dshcs-card"+(r?" dshcs-cardOpen":"")},s.createElement("button",{type:"button",className:"dshcs-cardHeader","aria-expanded":r,onClick:function(){n(!r)}},s.createElement("span",{className:"dshcs-cardHeadText"},s.createElement("span",{className:"dshcs-cardName"},a),s.createElement("span",{className:"dshcs-cardDescription"},e.description)),t.dirty===!0?s.createElement("span",{className:"dshcs-pending"},e.unsavedLabel):null,s.createElement(zr,{className:"dshcs-cardChevron"+(r?" dshcs-cardChevronOpen":"")})),r===!0?s.createElement("div",{className:"dshcs-cardBody"},i===!0?null:t.writable!==!0?s.createElement("p",{className:"dshcs-cardReadOnly",role:"status"},e.readOnlyLabel):null,e.children,i===!0?null:s.createElement("div",{className:"dshcs-cardFooter"},t.failed===!0?s.createElement("p",{className:"dshcs-cardFailed",role:"status"},e.saveFailedLabel):null,s.createElement(Q,{disabled:t.dirty!==!0||t.saving===!0,onClick:e.onDiscard},e.discardLabel),s.createElement(Q,{variant:"primary",disabled:o,onClick:e.onSave},t.saving===!0?e.savingLabel:e.saveLabel))):null)}function Pe(e){try{let y=function(){x("/code-server/status").then(function(g){g!=null&&typeof g=="object"&&L({status:g})}).catch(function(){})};var r=e.scope,[n,t]=s.useState(function(){return r!==void 0?r.getSnapshot():null}),[a,i]=s.useState(null),[o,c]=s.useState(!1),[p,f]=s.useState(!1),w=ue(),[v,E]=s.useState(ke());if(s.useEffect(function(){return Ee(function(g){E(g)})},[]),s.useEffect(function(){if(r===void 0||typeof r.subscribe!="function")return;function g(){try{t(r.getSnapshot())}catch(N){}}var b=r.subscribe(g);return function(){typeof b=="function"&&b()}},[r]),r===void 0)return console.error("[code-server] card scope missing"),null;if(n==null||n.status!=="ready")return n!=null&&n.status==="unavailable"&&console.warn("[code-server] settings scope unavailable:",n.status),null;var d=n.value!==void 0&&n.value!==null?n.value:{},m=n.user,h={keepResident:d.keepResident!==!1,claimExtensions:typeof d.claimExtensions=="string"?d.claimExtensions:$,fullscreenOnOpen:d.fullscreenOnOpen!==!1};if(w!=null&&w.sidebarUi==="legacy")return s.createElement(Me,{title:"Code Server",description:"\u5F53\u524D DSH \u7248\u672C\u4E0D\u53D7\u652F\u6301(\u7F3A\u5C11\u53F3\u4FA7\u680F\u670D\u52A1)",defaultOpen:!0,noticeOnly:!0,state:{available:!0,writable:!1,dirty:!1,invalid:!1,saving:!1,failed:!1}},s.createElement("div",{className:"dshcs-field"},s.createElement("div",{className:"dshcs-legacy"},"\u672C\u63D2\u4EF6\u81EA 0.2.3 \u8D77\u4E0D\u518D\u517C\u5BB9\u65E7\u7248 DSH\u3002"),s.createElement("div",{className:"dshcs-hint",style:{marginTop:0}},`\u672A\u68C0\u6D4B\u5230\u53F3\u4FA7\u680F\u63D2\u4EF6\u670D\u52A1 sidebarRightTabs / sidebarRight,\u56E0\u6B64\u63D2\u4EF6\u4E0D\u63D0\u4F9B\u4EFB\u4F55\u5165\u53E3(\u65E7\u7248\u7684\u60AC\u6D6E\u7403\u4E0E\u6D6E\u52A8\u7A97\u53E3\u5DF2\u79FB\u9664),\u4E5F\u4E0D\u4F1A\u540E\u53F0\u542F\u52A8 IDE\u3002
|
|
4
|
-
\u5347\u7EA7 DSH \u5230\u5E26\u53F3\u4FA7\u680F\u7684\u7248\u672C(\u2265 0.1.5-alpha.1)\u540E,Code Server \u4F1A\u51FA\u73B0\u5728\u53F3\u4FA7\u680F\u6807\u7B7E\u91CC,\u672C\u9875\u540C\u65F6\u663E\u793A\u5B8C\u6574\u8BBE\u7F6E\u9879;\u5347\u7EA7\u540E\u65E0\u9700\u91CD\u88C5\u672C\u63D2\u4EF6,\u5237\u65B0\u9875\u9762\u5373\u53EF\u3002`)));var k=m!=null&&Object.prototype.hasOwnProperty.call(m,"keepResident"),V=m!=null&&Object.prototype.hasOwnProperty.call(m,"claimExtensions"),_=m!=null&&Object.prototype.hasOwnProperty.call(m,"fullscreenOnOpen"),S=a!==null&&(a.keepResident!==h.keepResident||A(a.claimExtensions)!==A(h.claimExtensions)||a.fullscreenOnOpen!==h.fullscreenOnOpen),I=!S||o,U={available:!0,writable:n.writable,dirty:S,invalid:!1,saving:o,failed:p};async function C(){if(S&&!o){c(!0),f(!1);try{var g=a!==null?a:h;await e.scope.set("keepResident",g.keepResident===!0),await e.scope.set("claimExtensions",A(g.claimExtensions)),await e.scope.set("fullscreenOnOpen",g.fullscreenOnOpen===!0),i(null),y()}catch(b){f(!0)}c(!1)}}async function M(){if(!(o||n.writable!==!0)){c(!0),f(!1);try{typeof e.scope.unset=="function"?(await e.scope.unset("keepResident"),await e.scope.unset("claimExtensions"),await e.scope.unset("fullscreenOnOpen")):(await e.scope.set("keepResident",d.keepResident===void 0||d.keepResident===null?!0:d.keepResident),await e.scope.set("claimExtensions",typeof d.claimExtensions=="string"?A(d.claimExtensions):$),await e.scope.set("fullscreenOnOpen",d.fullscreenOnOpen===void 0||d.fullscreenOnOpen===null?!0:d.fullscreenOnOpen)),i(null),y()}catch(g){f(!0)}c(!1)}}var T=[s.createElement("div",{className:"dshcs-field"},s.createElement("div",{className:"dshcs-fieldHead"},s.createElement("span",{className:"dshcs-fieldLabel"},"\u8BA4\u9886\u7C7B\u578B(\u6309\u6269\u5C55\u540D,\u5206\u53F7\u5206\u9694)"),V===!0?s.createElement(oe,{overridden:!0,disabled:n.writable!==!0,overriddenLabel:"\u5DF2\u8986\u76D6",resetLabel:"\u6062\u590D\u9ED8\u8BA4",onReset:function(){M()}}):null),s.createElement(Pr,{value:a!==null?a.claimExtensions:h.claimExtensions,disabled:n.writable!==!0,placeholder:"\u4F8B\u5982 py;ts;!md(\u7559\u7A7A = \u4E0D\u8BA4\u9886\u4EFB\u4F55\u6587\u4EF6)",onChange:function(g){i(function(b){return Object.assign({},b!==null?b:h,{claimExtensions:g})}),f(!1)}}),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},"\u5199\u6269\u5C55\u540D(\u4E0D\u5E26\u70B9,\u5927\u5C0F\u5199\u968F\u610F);`*` = \u5176\u4F59\u7C7B\u578B\u4E5F\u8BA4\u9886;`!ext` = \u4E0D\u8BA4\u9886(\u6392\u9664\u4F18\u5148)\u3002\u4E0D\u518D\u533A\u5206\u4F1A\u8BDD\u5185/\u7EDD\u5BF9\u8DEF\u5F84 \u2014\u2014 \u6240\u6709 `dsh-resource://file/**` \u4E00\u89C6\u540C\u4EC1,\u672A\u8BA4\u9886\u7684\u843D\u56DE DSH \u81EA\u5E26\u9884\u89C8\u3002"),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},"\u5B9E\u9645\u89C4\u5219:"+ye(a!==null?a.claimExtensions:h.claimExtensions)+(A(a!==null?a.claimExtensions:h.claimExtensions)!==(a!==null?a.claimExtensions:h.claimExtensions)?"(\u4FDD\u5B58\u540E\u5F52\u4E00\u5316)":"")),s.createElement("div",{className:"dshcs-hint",style:{marginTop:6}},"\u9ED8\u8BA4\u6392\u9664\u4E09\u7C7B:\u2460 DSH \u9884\u89C8\u6E32\u67D3\u5F97\u597D\u7684 markdown/html/\u56FE\u7247/PDF;\u2461 \u53EF\u6267\u884C\u6587\u4EF6\u4E0E\u4E8C\u8FDB\u5236\u4EA7\u7269(exe/dll/msi/jar/so\u2026);\u2462 Office \u4E0E\u7248\u5F0F\u6587\u6863(docx/xlsx/pptx/vsdx\u2026)\u3002\u6587\u672C\u811A\u672C(bat/cmd/ps1/sh/py)\u4E0E csv/tsv \u4ECD\u8FDB IDE\u3002\u60F3\u6574\u7EC4\u653E\u5F00,\u628A\u4E0B\u9762\u8FD9\u6BB5\u6362\u6210 `*;!md;!html;!png;!jpg;!pdf` \u4E4B\u7C7B\u7684\u77ED\u6E05\u5355\u5373\u53EF\u3002"),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},"\u9ED8\u8BA4\u503C(\u53EF\u6574\u6BB5\u590D\u5236;\u6362\u884C\u4E0E\u5206\u53F7\u7B49\u4EF7):",s.createElement("code",{className:"dshcs-code","data-claim-default":"lines"},Tr()))),s.createElement("div",{className:"dshcs-field"},s.createElement("div",{className:"dshcs-fieldHead"},s.createElement("span",{className:"dshcs-fieldLabel"},"\u6253\u5F00\u5373\u5168\u5C4F(\u53F3\u4FA7\u680F\u94FA\u6EE1\u7A97\u53E3)"),_===!0?s.createElement(oe,{overridden:!0,disabled:n.writable!==!0,overriddenLabel:"\u5DF2\u8986\u76D6",resetLabel:"\u6062\u590D\u9ED8\u8BA4",onReset:function(){M()}}):null),s.createElement(Ie,{checked:a!==null?a.fullscreenOnOpen:h.fullscreenOnOpen,disabled:n.writable!==!0,onChange:function(g){i(function(b){return Object.assign({},b!==null?b:h,{fullscreenOnOpen:g===!0})}),f(!1)}},'\u6253\u5F00 Code Server \u6807\u7B7E(\u542B\u70B9\u5F00\u4EA7\u7269/\u4EA4\u4ED8\u6587\u4EF6)\u65F6,\u81EA\u52A8\u628A\u53F3\u4FA7\u680F\u4ECE"\u4E0E\u5BF9\u8BDD\u5E76\u6392"\u5207\u5230\u5168\u5C4F;\u60F3\u540C\u65F6\u770B\u5BF9\u8BDD\u5C31\u5173\u6389\u5B83,\u6216\u968F\u65F6\u70B9\u53F3\u4FA7\u680F\u7684\u300C\u9000\u51FA\u5168\u5C4F\u300D\u2014\u2014 \u672C\u6B21\u6253\u5F00\u4E0D\u4F1A\u88AB\u62A2\u56DE\u53BB'),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},'\u53EA\u5F71\u54CD"\u6253\u5F00\u90A3\u4E00\u523B":\u5207\u8D70\u518D\u5207\u56DE\u3001\u518D\u6B21\u6253\u5F00\u6587\u4EF6 tab \u4F1A\u91CD\u65B0\u5207\u5168\u5C4F;DSH \u672A\u5411\u63D2\u4EF6\u5F00\u653E\u6A21\u5F0F\u63A5\u53E3,\u672C\u9879\u7531\u70B9\u51FB\u53F3\u4FA7\u680F\u81EA\u8EAB\u7684\u300C\u5168\u5C4F\u300D\u6309\u94AE\u5B9E\u73B0\u3002')),s.createElement("div",{className:"dshcs-field"},s.createElement("div",{className:"dshcs-fieldHead"},s.createElement("span",{className:"dshcs-fieldLabel"},"\u540E\u53F0\u5E38\u9A7B(\u5207\u6807\u7B7E\u4E0D\u91CD\u8F7D)"),k===!0?s.createElement(oe,{overridden:!0,disabled:n.writable!==!0,overriddenLabel:"\u5DF2\u8986\u76D6",resetLabel:"\u6062\u590D\u9ED8\u8BA4",onReset:function(){M()}}):null),s.createElement(Ie,{checked:a!==null?a.keepResident:h.keepResident,disabled:n.writable!==!0,onChange:function(g){i(function(b){return Object.assign({},b!==null?b:h,{keepResident:g===!0})}),f(!1)}},'\u5F00\u542F\u540E\u5BBF\u4E3B\u542F\u52A8\u5373\u628A IDE \u52A0\u8F7D\u5230\u540E\u53F0"\u505C\u653E\u533A":\u5207\u6362\u53F3\u4FA7\u680F\u6807\u7B7E\u3001\u6536\u8D77/\u5C55\u5F00\u4FA7\u680F\u3001\u62D6\u6210\u6D6E\u52A8\u7A97\u53E3\u90FD\u4E0D\u518D\u91CD\u8F7D,\u9996\u6B21\u6253\u5F00\u514D\u7B49\u5F85;\u5173\u95ED\u5219\u53EA\u5728\u6253\u5F00\u9762\u677F\u65F6\u52A0\u8F7D(\u7701\u5185\u5B58)'),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},j?"\u5E38\u9A7B\u9762:"+(v.docked?"\u5DF2\u505C\u9760":v.ready?"\u5DF2\u505C\u653E(\u540E\u53F0\u8FD0\u884C\u4E2D)":"\u672A\u542F\u52A8")+(v.degraded?" \xB7 \u672C\u6B21\u53D1\u751F\u8FC7\u964D\u7EA7\u91CD\u8F7D":""):"\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301 Element.moveBefore(Chromium <133)\u2192 \u5E38\u9A7B\u4E0D\u53EF\u7528,\u5207\u6807\u7B7E\u4ECD\u4F1A\u6574\u9875\u91CD\u8F7D(\u5347\u7EA7\u6D4F\u89C8\u5668\u540E\u81EA\u52A8\u751F\u6548)"))];return e.seat==="bundle-config"?s.createElement("div",{className:"dshcs-cfgpage","data-code-server-config":"page"},n.writable!==!0?s.createElement("p",{className:"dshcs-cardReadOnly",role:"status"},"\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002"):null,...T,p===!0?s.createElement("p",{className:"dshcs-cardFailed",role:"status"},"\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539\u3002"):null,s.createElement("div",{className:"dshcs-cfgfoot"},s.createElement(Q,{disabled:!S||o,onClick:function(){i(null),f(!1)}},"\u653E\u5F03\u4FEE\u6539"),s.createElement(Q,{variant:"primary",disabled:I,onClick:function(){C()}},o===!0?"\u4FDD\u5B58\u4E2D\u2026":"\u4FDD\u5B58"))):s.createElement(Me,{title:"Code Server",description:"\u5165\u53E3:\u53F3\u4FA7\u680F\u6807\u7B7E(DSH \u2265 0.1.5-alpha.1);\u8BA4\u9886\u7C7B\u578B\u51B3\u5B9A\u54EA\u4E9B\u6587\u4EF6\u4EA4\u7ED9 Code Server \u6253\u5F00",state:U,unsavedLabel:"\u672A\u4FDD\u5B58",readOnlyLabel:"\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002",saveFailedLabel:"\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539\u3002",discardLabel:"\u653E\u5F03\u4FEE\u6539",saveLabel:"\u4FDD\u5B58",savingLabel:"\u4FDD\u5B58\u4E2D\u2026",onSave:C,onDiscard:function(){i(null),f(!1)}},...T)}catch(y){return console.error("[code-server] card render error:",y!=null&&y.message!==void 0?y.message:String(y)),null}}function _r(e){return e!=null&&e.view==="summary"?Er:s.createElement(Pe,Object.assign({},e,{seat:"bundle-config"}))}var Re=900,u={rev:-1,open:!1,mounted:!1,pending:null,el:null,body:null,title:null,timer:null,loading:!1};function Ur(e){if(!(e===null||typeof e!="object")){if(e.type==="ready"){R(!0);return}if(e.type==="ask"&&typeof e.text=="string"){x("/code-server/ask/send",{text:e.text,mode:u.pending===null?void 0:u.pending.mode}).then(function(r){u.rev=-1,R(!0)}).catch(function(){});return}if(e.type==="approve"&&typeof e.id=="string"){x("/code-server/ask/approve",{id:e.id,outcome:e.outcome}).then(function(r){r!=null&&r.ok===!1&&J(r.error||"\u6388\u6743\u63D0\u4EA4\u5931\u8D25"),u.rev=-1,R(!0)}).catch(function(r){J("\u6388\u6743\u63D0\u4EA4\u5931\u8D25:"+String(r&&r.message?r.message:r))});return}e.type==="close"&&_e()}}function R(e){if(u.el!==null){var r=window.__DSHCS_ASK_PUSH__;typeof r=="function"?r(u.pending):e===!0&&setTimeout(function(){R(!1)},120)}}function J(e){console.warn("[code-server] ask dialog:",e)}function _e(){u.open=!1,u.el!==null&&(u.el.style.display="none"),x("/code-server/ask/close",{}).catch(function(){})}function Ue(){if(u.el===null){var e=document.createElement("div");e.className="dshcs-dialog",e.setAttribute("data-dshcs-dialog","ask"),e.style.cssText=["position:fixed","right:24px","bottom:24px","width:460px","height:600px","min-width:320px","min-height:280px","max-width:96vw","max-height:92vh","resize:both","overflow:hidden","display:none","z-index:2147483000","border-radius:10px","box-shadow:0 18px 48px rgba(0,0,0,.45)","border:1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.4))","background:var(--dsw-alias-bg-base, var(--vscode-editor-background, #1e1e1e))"].join(";");var r=document.createElement("div");r.style.cssText="display:flex;align-items:center;gap:8px;padding:6px 10px;cursor:move;border-bottom:1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.3));font:var(--dsw-font-markdown-base, 13px/20px sans-serif)";var n=document.createElement("span");n.textContent="DSH \u5BF9\u8BDD",n.style.cssText="font-weight:600;flex:none";var t=document.createElement("span");t.style.cssText="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary, rgba(128,128,128,.9));font-size:12px";var a=document.createElement("button");a.type="button",a.textContent="\u2715",a.title="\u5173\u95ED",a.setAttribute("aria-label","\u5173\u95ED"),a.style.cssText="flex:none;border:0;background:transparent;color:inherit;cursor:pointer;font-size:13px;line-height:1;padding:2px 6px;border-radius:4px",a.addEventListener("click",function(){_e()}),r.appendChild(n),r.appendChild(t),r.appendChild(a);var i=document.createElement("div");i.className="dshcs-panel",i.setAttribute("data-dshcs-ask-root",""),i.style.cssText="height:calc(100% - 33px);overflow:hidden",e.appendChild(r),e.appendChild(i),document.body.appendChild(e),u.el=e,u.body=i,u.title=t;var o=null;r.addEventListener("pointerdown",function(c){if(c.target!==a){var p=e.getBoundingClientRect();o={x:c.clientX,y:c.clientY,left:p.left,top:p.top},e.style.right="auto",e.style.bottom="auto",e.style.left=p.left+"px",e.style.top=p.top+"px";try{r.setPointerCapture(c.pointerId)}catch(f){}}}),r.addEventListener("pointermove",function(c){o!==null&&(e.style.left=Math.max(0,Math.min(window.innerWidth-80,o.left+(c.clientX-o.x)))+"px",e.style.top=Math.max(0,Math.min(window.innerHeight-40,o.top+(c.clientY-o.y)))+"px")}),r.addEventListener("pointerup",function(){o=null})}}var He="dshcs.ask.bundle",Hr=900*1e3,O=null;function de(){try{var e=window.sessionStorage.getItem(He);if(e==null)return null;var r=JSON.parse(e);return r==null||typeof r.js!="string"||typeof r.css!="string"||Date.now()-Number(r.at||0)>Hr?null:{css:r.css,js:r.js}}catch(n){return null}}function Fe(e){try{window.sessionStorage.setItem(He,JSON.stringify({at:Date.now(),css:e.css,js:e.js}))}catch(r){}}function Fr(){if(!(O!==null||u.mounted)){var e=de();if(e!==null){O=e;return}x("/code-server/ask/bundle").then(function(r){r==null||r.ok!==!0||(O={css:r.css,js:r.js},Fe(O),console.log("[code-server] ask bundle preloaded ("+Math.round((r.js.length+r.css.length)/1024)+"KB)"))}).catch(function(){})}}function Br(){if(!(u.mounted||u.loading)){u.loading=!0;var e=O!==null?Promise.resolve(O):de()!==null?Promise.resolve(de()):x("/code-server/ask/bundle");e.then(function(r){u.loading=!1;var n=r;if(O===null&&r!=null&&r.ok===!0&&(n={css:r.css,js:r.js},O=n,Fe(n)),n==null||typeof n.js!="string"){J(n!=null&&n.error?n.error:"\u9762\u677F\u4EA7\u7269\u4E0D\u53EF\u7528");return}if(Ue(),document.getElementById("dshcs-ask-style")===null){var t=document.createElement("style");t.id="dshcs-ask-style",t.textContent=n.css,document.head.appendChild(t)}window.__DSHCS_HOST__="dsh",window.__DSHCS_MOUNT__=u.body,window.acquireVsCodeApi=function(){return{postMessage:Ur}};var a=document.createElement("script");a.textContent=n.js,document.head.appendChild(a),u.mounted=!0,setTimeout(function(){R(!0)},60)}).catch(function(r){u.loading=!1,J("\u9762\u677F\u4EA7\u7269\u52A0\u8F7D\u5931\u8D25:"+String(r&&r.message?r.message:r))})}}function je(){x("/code-server/ask/state?rev="+u.rev).then(function(e){e==null||e.ok!==!0||e.changed===!0&&(u.rev=typeof e.rev=="number"?e.rev:-1,u.pending=e,e.open===!0?(Ue(),u.el.style.display="block",u.title!==null&&(u.title.textContent=e.contextText||"\u6765\u81EA\u7F16\u8F91\u5668"),u.open=!0,Br(),u.mounted&&R(!0)):u.el!==null&&(u.el.style.display="none",u.open=!1))}).catch(function(){})}function Wr(){u.timer===null&&(u.timer=setInterval(je,Re),setTimeout(je,800),setTimeout(Fr,3e3),console.log("[code-server] ask dialog poller started (every "+Re+"ms)"))}function Yr(e){try{Gr(e),Wr()}catch(r){console.error("[code-server] apply failed:",r&&r.stack?r.stack:String(r));try{document.title="CS-ERR "+(r&&r.message||String(r))}catch(n){}}}function Gr(e){var r=e.get("slots");if(r===void 0){console.error("[code-server] slots service unavailable");return}var n=e.settingsScope,t=null;if(n!==void 0&&typeof n.bind=="function")t=n.bind({namespace:"code-server"});else{console.error("[code-server] settingsScope unavailable (inject missing?); settings card disabled");return}x("/code-server/status").then(function(d){L({status:d})}).catch(function(){}),r.inject("plugins.bundle.config",()=>r.register({name:"plugins.bundle.config",key:wr,inject:function(){return{scope:t}}},_r)),r.inject("settings.plugin.item",()=>r.register({name:"settings.plugin.item",key:"code-server",label:"Code Server",inject:function(){return{scope:t}}},Pe));var a=!1;function i(d,m){if(!a){a=!0,w();var h=!1;try{h=Mr(d)}catch(k){console.error("[code-server] sidebar tab registration failed:",k!=null&&k.message!=null?k.message:String(k))}P.sidebarUi==="legacy"&&(console.log("[code-server] \u53F3\u4FA7\u680F\u670D\u52A1\u665A\u5230(\u7ECF "+m+"):\u64A4\u9500\u65E7\u7248 DSH \u5224\u5B9A"),x("/code-server/ui-mode",{sidebar:!0}).catch(function(){})),L({sidebarUi:"modern",sidebarRegisterFailed:h!==!0}),r.inject("shell.overlay",()=>r.register({name:"shell.overlay",id:"code-server",order:70,label:"Code Server"},k=>s.createElement(yr,k))),console.log("[code-server] client registered via "+m+": sidebar tab="+h+"(claims dsh-resource://file/**) + resident preload + settings card")}}function o(d){L({sidebarUi:"legacy"}),console.warn("[code-server] \u672A\u63A2\u6D4B\u5230\u53F3\u4FA7\u680F\u670D\u52A1(sidebarRightTabs/sidebarRight): \u672C\u63D2\u4EF6\u81EA 0.2.3 \u8D77\u4E0D\u518D\u517C\u5BB9\u65E7\u7248 DSH \u2014\u2014 \u9664\u300C\u8BBE\u7F6E \u2192 \u63D2\u4EF6 \u2192 Code Server\u300D\u7684\u63D0\u793A\u5916\u4E0D\u63D0\u4F9B\u4EFB\u4F55\u5165\u53E3,\u4E5F\u4E0D\u9884\u70ED IDE\u3002\u8BF7\u5347\u7EA7 DSH\u3002"),d===!0&&x("/code-server/ui-mode",{sidebar:!1}).catch(function(){})}var c=null,p=null,f=null;function w(){c!==null&&(clearTimeout(c),c=null),p!==null&&(clearTimeout(p),p=null),f!==null&&(clearTimeout(f),f=null)}var v=!1;try{typeof e.inject=="function"&&(v=!0,e.inject(["sidebarRightTabs","sidebarRight"],function(d){i(d,"inject")}))}catch(d){console.warn("[code-server] sidebar inject failed:",d!=null&&d.message!=null?d.message:String(d))}var E=typeof e.get=="function"&&e.get("sidebarRightTabs")!==void 0&&e.get("sidebarRight")!==void 0;if(E){f=setTimeout(function(){a||(console.warn("[code-server] ctx.inject \u672A\u5728 1.5s \u5185\u56DE\u8C03,\u6539\u7528\u540C\u6B65\u670D\u52A1\u6CE8\u518C\u4FA7\u680F"),i(e,"sync-fallback"))},Cr);return}if(v!==!0){o(!0);return}c=setTimeout(function(){a||o(!1)},kr),p=setTimeout(function(){a||o(!0)},Sr)}var Xr=["slots","settingsScope"],Qr="code-server";
|
|
5
|
-
return module.exports;}});
|
|
6
22
|
|
|
23
|
+
// ================================ 地址语法 —— 原 src/address.js(0.3.58 起内联:客户端不再构建) ================================
|
|
24
|
+
/**
|
|
25
|
+
* dsh-code-server — DSH 资源地址解析(0.2.5;0.2.11 起不再区分认领作用域)
|
|
26
|
+
*
|
|
27
|
+
* 官方 `openFile(path, { line? })`(产物 chip、"交付"卡片预览、正文内联提及)最终都变成
|
|
28
|
+
* `dsh-resource://file/…` 地址交给 `ctx.sidebarRight.openResource`,由注册了匹配 `patterns`
|
|
29
|
+
* 的 tab 类型认领 —— 认领地址即是官方入口的文件查看器。地址语法对齐 DSH
|
|
30
|
+
* `packages/util/workspace-path/src/file-address.ts`(session 作用域 / absolute 作用域、
|
|
31
|
+
* 段做 component 编码、`:` 保持字面量、忽略查询串);页面 tab 记在 `sidebar://<kind>` 下。
|
|
32
|
+
*
|
|
33
|
+
* 本模块只做"地址 → 路径"的解析;**认领与否按文件类型判断,见 lib/claim-types.js**
|
|
34
|
+
* (session 与 absolute 一视同仁)。纯字符串处理,不碰文件系统 → 可离线单测。
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** 文件地址前缀。 */
|
|
38
|
+
const FILE_PREFIX = 'dsh-resource://file/'
|
|
39
|
+
/** 右侧栏给"页面 tab"记的地址前缀(`sidebar://<kind>`)。 */
|
|
40
|
+
const PAGE_PREFIX = 'sidebar://'
|
|
41
|
+
|
|
42
|
+
/** 是否为右侧栏的"页面 tab"地址(`sidebar://<kind>`)。 */
|
|
43
|
+
function isPageAddress(address) {
|
|
44
|
+
return typeof address === 'string' && address.startsWith(PAGE_PREFIX)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 解析文件地址(与 DSH `parseFileAddress` 同语义));失败返回 null。 */
|
|
48
|
+
function parseFileAddress(address) {
|
|
49
|
+
if (typeof address !== 'string' || !address.startsWith(FILE_PREFIX)) return null
|
|
50
|
+
// 查询串/片段不参与路径
|
|
51
|
+
var cut = address.length
|
|
52
|
+
for (var i = 0; i < address.length; i += 1) {
|
|
53
|
+
var ch = address.charAt(i)
|
|
54
|
+
if (ch === '?' || ch === '#') { cut = i; break }
|
|
55
|
+
}
|
|
56
|
+
var parts = address.slice(FILE_PREFIX.length, cut).split('/')
|
|
57
|
+
var scope = parts.shift()
|
|
58
|
+
try {
|
|
59
|
+
if (scope === 'session') {
|
|
60
|
+
var id = parts.shift()
|
|
61
|
+
if (id === undefined || id === '' || parts.length === 0) return null
|
|
62
|
+
return { scope: 'session', sessionId: decodeURIComponent(id), path: decodeSegmentPath(parts) }
|
|
63
|
+
}
|
|
64
|
+
if (scope === 'absolute') {
|
|
65
|
+
// `absolute//server/share/x`(UNC)首段为空且后面还有内容
|
|
66
|
+
var unc = parts[0] === '' && parts.length > 1
|
|
67
|
+
var segments = unc ? parts.slice(1) : parts
|
|
68
|
+
var decoded = decodeSegmentPath(segments)
|
|
69
|
+
if (decoded === '' || decoded.charAt(0) === '/') {
|
|
70
|
+
if (!unc) return null
|
|
71
|
+
}
|
|
72
|
+
if (unc) return { scope: 'absolute', path: '//' + decoded }
|
|
73
|
+
if (decoded === '') return null
|
|
74
|
+
return { scope: 'absolute', path: isDrivePath(decoded) ? decoded : '/' + decoded }
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return null // decodeURIComponent 对非法转义抛 URIError
|
|
78
|
+
}
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function decodeSegmentPath(segments) {
|
|
83
|
+
var out = []
|
|
84
|
+
for (var i = 0; i < segments.length; i += 1) out.push(decodeURIComponent(segments[i]))
|
|
85
|
+
return out.join('/')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** `C:/x` 形式的盘符路径(地址里 `:` 是字面量)。 */
|
|
89
|
+
function isDrivePath(path) {
|
|
90
|
+
return /^[A-Za-z]:($|\/)/.test(path)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 路径是否已是绝对路径(POSIX `/x`、Windows `C:/x`、UNC `//server/share`)。 */
|
|
94
|
+
function isAbsoluteFilePath(path) {
|
|
95
|
+
if (typeof path !== 'string' || path === '') return false
|
|
96
|
+
if (path.charAt(0) === '/') return true
|
|
97
|
+
return isDrivePath(path)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 地址末段(用作 tab 标题),按段解码;取不到时返回空串。 */
|
|
101
|
+
function basenameOfAddress(address) {
|
|
102
|
+
var parsed = parseFileAddress(address)
|
|
103
|
+
if (parsed === null) return ''
|
|
104
|
+
var path = parsed.path.replace(/[/\\]+$/, '')
|
|
105
|
+
var at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
|
106
|
+
var name = at === -1 ? path : path.slice(at + 1)
|
|
107
|
+
return name
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 把地址里的路径解析成给 VS Code 用的绝对路径;相对路径且 cwd 未知时 null。 */
|
|
111
|
+
function resolveFilePath(parsed, cwd) {
|
|
112
|
+
if (parsed === null || parsed == null) return null
|
|
113
|
+
var path = typeof parsed.path === 'string' ? parsed.path : ''
|
|
114
|
+
if (path === '') {
|
|
115
|
+
// 会话根自身:交给工作区(不打开具体文件)
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
if (isAbsoluteFilePath(path)) return path
|
|
119
|
+
if (typeof cwd !== 'string' || cwd === '') return null
|
|
120
|
+
var sep = cwd.indexOf('\\') !== -1 ? '\\' : '/'
|
|
121
|
+
var root = cwd.replace(/[\\/]+$/, '')
|
|
122
|
+
return root + sep + (sep === '\\' ? path.replace(/\//g, '\\') : path)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ================================ 当前工作区目录解析 —— 原 src/workspace.js(0.3.58 起内联:客户端不再构建) ================================
|
|
126
|
+
// —— 解析"当前工作区目录"(决定 workbench 用哪个 `?folder=` 打开)
|
|
127
|
+
//
|
|
128
|
+
// 为什么单独成模块:这段逻辑的真实输入是 **DSH 客户端 store 的形状**,而它随 DSH 版本变过 ——
|
|
129
|
+
// · DSH ≤ 0.1.6-alpha.1:`SessionListState` 带 `current`(选中的会话 id 就挂在会话列表快照上,
|
|
130
|
+
// 老写法 `useSessions(s => s).current` 即"当前会话");
|
|
131
|
+
// · DSH ≥ 0.1.6-alpha.2:`current` 被移出列表 store(上游 refactor 的注释:view selection remains
|
|
132
|
+
// outside the Controller;快照只剩 `{ ids, byId, phase, subagentsByParent, jobsBySession }`),
|
|
133
|
+
// 会话作用域插槽改为用标准 prop `sessionId` 告诉 body"你是哪个会话的" —— 官方自己的右侧栏标签
|
|
134
|
+
// 就是这么读 cwd 的(ui-deliverables 的 ReviewTab:`useSessions(s => s.byId[sessionId]?.cwd)`)。
|
|
135
|
+
// 只认一种形状 ⇒ 在另一版上静默拿到 undefined:0.3.46 在 0.1.6-alpha.2 上就是这样 —— 客户端不再
|
|
136
|
+
// 发 cwd,IDE 以**空工作区**启动(pid.json 里 cwd/launchCwd 双空)。所以这里按
|
|
137
|
+
// 「会话标准 prop → 旧版 current → 工作区表补位」的顺序取,并把判断留在纯函数里(可离线单测,
|
|
138
|
+
// 见 scripts/test-workspace-cwd.mjs;hook 一律留在组件内,本模块不碰 React)。
|
|
139
|
+
//
|
|
140
|
+
// 全部输入都是"可能不存在"的:拿不到就返回 undefined,调用方不传 cwd(/start 不带 cwd = 保留当前
|
|
141
|
+
// 目录),**绝不猜一个目录** —— 猜错会把 IDE 开到别人的工作区里。
|
|
142
|
+
|
|
143
|
+
/** 会话摘要里的 cwd(空串/非字符串一律视为缺失)。 */
|
|
144
|
+
function cwdOf(sessions, sessionId) {
|
|
145
|
+
if (sessions == null || sessions.byId == null) return undefined
|
|
146
|
+
if (typeof sessionId !== 'string' || sessionId === '') return undefined
|
|
147
|
+
var row = sessions.byId[sessionId]
|
|
148
|
+
if (row == null) return undefined
|
|
149
|
+
return typeof row.cwd === 'string' && row.cwd !== '' ? row.cwd : undefined
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** 工作区表里"包含该会话"的那个工作区的路径(DSH ≤ 0.1.6-alpha.1 的 currentAddress 语义由它兜底)。 */
|
|
153
|
+
function pathHoldingSession(workspaces, sessionId) {
|
|
154
|
+
if (workspaces == null || !Array.isArray(workspaces.items)) return undefined
|
|
155
|
+
if (typeof sessionId !== 'string' || sessionId === '') return undefined
|
|
156
|
+
for (var i = 0; i < workspaces.items.length; i += 1) {
|
|
157
|
+
var w = workspaces.items[i]
|
|
158
|
+
if (w == null || !Array.isArray(w.sessionIds)) continue
|
|
159
|
+
if (w.sessionIds.indexOf(sessionId) === -1) continue
|
|
160
|
+
if (typeof w.path === 'string' && w.path !== '') return w.path
|
|
161
|
+
}
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 取当前工作区目录。
|
|
167
|
+
* @param input - `{ sessionId?, sessions?, workspaces? }`
|
|
168
|
+
* · sessionId:会话作用域标准 prop(根作用域没有);文件 tab 用地址里的会话 id
|
|
169
|
+
* · sessions:`useSessions(s => s)` 的快照(服务缺失时 null)
|
|
170
|
+
* · workspaces:`useWorkspaces(s => s)` 的快照(服务缺失时 null)
|
|
171
|
+
* @returns 绝对路径;判断不出来时 undefined(调用方不传 cwd,由宿主保留当前目录)
|
|
172
|
+
*/
|
|
173
|
+
function pickWorkspaceCwd(input) {
|
|
174
|
+
var opts = input == null ? {} : input
|
|
175
|
+
var sessions = opts.sessions == null ? null : opts.sessions
|
|
176
|
+
var workspaces = opts.workspaces == null ? null : opts.workspaces
|
|
177
|
+
var sessionId = typeof opts.sessionId === 'string' && opts.sessionId !== '' ? opts.sessionId : undefined
|
|
178
|
+
|
|
179
|
+
// 1) 会话自带的 cwd(新 DSH:标准 prop 给 sessionId;两条版本都吃这一条)
|
|
180
|
+
var fromSession = cwdOf(sessions, sessionId)
|
|
181
|
+
if (fromSession !== undefined) return fromSession
|
|
182
|
+
|
|
183
|
+
// 2) 旧版 DSH:列表快照上的 current
|
|
184
|
+
var legacyCurrent = sessions == null ? undefined : sessions.current
|
|
185
|
+
var fromLegacy = cwdOf(sessions, legacyCurrent)
|
|
186
|
+
if (fromLegacy !== undefined) return fromLegacy
|
|
187
|
+
|
|
188
|
+
// 3) 工作区表:该会话所属的工作区
|
|
189
|
+
var fromWorkspace = pathHoldingSession(workspaces, sessionId)
|
|
190
|
+
if (fromWorkspace !== undefined) return fromWorkspace
|
|
191
|
+
var fromLegacyWorkspace = pathHoldingSession(workspaces, typeof legacyCurrent === 'string' ? legacyCurrent : undefined)
|
|
192
|
+
if (fromLegacyWorkspace !== undefined) return fromLegacyWorkspace
|
|
193
|
+
|
|
194
|
+
// 4) 根作用域(后台预热)没有 sessionId:退到"最近活跃的那个会话"所属的工作区。
|
|
195
|
+
// host 的会话列表按活跃度排序(list() 文档:ordered by activity),所以 ids[0] 就是它。
|
|
196
|
+
var recent = sessions != null && Array.isArray(sessions.ids) ? sessions.ids[0] : undefined
|
|
197
|
+
var fromRecent = pathHoldingSession(workspaces, typeof recent === 'string' ? recent : undefined)
|
|
198
|
+
if (fromRecent !== undefined) return fromRecent
|
|
199
|
+
|
|
200
|
+
// 5) 旧字段兼容(recentWorkspaceId 在现版 WorkspaceSnapshot 里已不存在,留着不害事)
|
|
201
|
+
if (workspaces != null && Array.isArray(workspaces.items) && workspaces.recentWorkspaceId !== undefined) {
|
|
202
|
+
for (var j = 0; j < workspaces.items.length; j += 1) {
|
|
203
|
+
var item = workspaces.items[j]
|
|
204
|
+
if (item == null || item.workspaceId !== workspaces.recentWorkspaceId) continue
|
|
205
|
+
if (typeof item.path === 'string' && item.path !== '') return item.path
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 6) 最后兜底:工作区表的第一个(hook 拿不到选中态时的既有行为)
|
|
210
|
+
if (workspaces != null && Array.isArray(workspaces.items) && workspaces.items.length > 0) {
|
|
211
|
+
var first = workspaces.items[0]
|
|
212
|
+
if (first != null && typeof first.path === 'string' && first.path !== '') return first.path
|
|
213
|
+
}
|
|
214
|
+
return undefined
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ================================ 常驻 IDE 面 —— 原 src/surface.js(0.3.58 起内联:客户端不再构建)(内联时 state → surfaceState:与插件主体的同名顶层变量撞名,94 处引用一并改名) ================================
|
|
218
|
+
/**
|
|
219
|
+
* dsh-code-server — 常驻 IDE 面(0.2.2)
|
|
220
|
+
*
|
|
221
|
+
* 背景(实测):ui-dockkit 只渲染当前激活标签的 body(TabPanel.tsx:412),切走即 React 卸载 =
|
|
222
|
+
* iframe 被移出文档 = 浏览上下文销毁;而普通 `appendChild` 移动 iframe 会让内部计时器归零(等价重载),
|
|
223
|
+
* `Element.moveBefore()`(Chromium ≥133)才是状态保持型原子移动(实测计时器 1→2 连续)。
|
|
224
|
+
* 因此 iframe 由本模块持有:激活时 `host.moveBefore(frame, null)` 停靠,失活时移回文档级 park
|
|
225
|
+
* 容器(离屏、保留最后尺寸、不销毁),只有 src 真变化(工作区/端口)才显式导航。
|
|
226
|
+
*
|
|
227
|
+
* 实测坑:曾出现"元素在、画面不重绘"(尺寸/命中/可见性全对但一片白);`translateZ(0)`、`opacity`
|
|
228
|
+
* 无效,`display:none → 强制重排 → 还原` 可在同一 JS 任务内恢复且不重载文档 —— 触发条件未复现,
|
|
229
|
+
* 故按兜底处理:每次"停放 → 停靠"补一次 `nudgeRepaint()`(`setNudgeEnabled(false)` 可现场 A/B)。
|
|
230
|
+
*
|
|
231
|
+
* 不支持 `moveBefore` 时退回 `appendChild`(会重载)并标记 `degraded`,由界面明示,不静默失败。
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
/** 状态保持型移动是否可用(Chromium ≥133)。 */
|
|
235
|
+
const supportsMoveBefore = typeof Element !== 'undefined'
|
|
236
|
+
&& typeof Element.prototype.moveBefore === 'function'
|
|
237
|
+
|
|
238
|
+
var PARK_OFFSCREEN_LEFT = -20000
|
|
239
|
+
|
|
240
|
+
var surfaceState = {
|
|
241
|
+
park: null,
|
|
242
|
+
frame: null,
|
|
243
|
+
currentSrc: null,
|
|
244
|
+
sameOrigin: null,
|
|
245
|
+
owner: null,
|
|
246
|
+
/** 最后一次有效停靠尺寸:停放时沿用它,避免 VS Code 触发 0×0 重排。 */
|
|
247
|
+
lastRect: null,
|
|
248
|
+
/** 'offscreen'(默认,省 CPU)| 'behind'(停在原位、压在面板下,规避不可见 iframe 节流)。 */
|
|
249
|
+
parkStrategy: 'offscreen',
|
|
250
|
+
degraded: false,
|
|
251
|
+
preloaded: false,
|
|
252
|
+
/** 陈旧合成表面修复计数(排障用:每次停靠最多 +1)。 */
|
|
253
|
+
nudgeCount: 0,
|
|
254
|
+
nudgeEnabled: true,
|
|
255
|
+
lastNudgeAt: 0,
|
|
256
|
+
listeners: new Set(),
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function snapshot() {
|
|
260
|
+
return {
|
|
261
|
+
ready: surfaceState.frame !== null,
|
|
262
|
+
docked: surfaceState.owner !== null,
|
|
263
|
+
owner: surfaceState.owner,
|
|
264
|
+
src: surfaceState.currentSrc,
|
|
265
|
+
sameOrigin: surfaceState.sameOrigin,
|
|
266
|
+
parkStrategy: surfaceState.parkStrategy,
|
|
267
|
+
degraded: surfaceState.degraded,
|
|
268
|
+
preloaded: surfaceState.preloaded,
|
|
269
|
+
supportsMoveBefore: supportsMoveBefore,
|
|
270
|
+
nudgeCount: surfaceState.nudgeCount,
|
|
271
|
+
lastNudgeAt: surfaceState.lastNudgeAt,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function notify() {
|
|
276
|
+
var snap = snapshot()
|
|
277
|
+
surfaceState.listeners.forEach(function (fn) {
|
|
278
|
+
try { fn(snap) } catch (e) { /* 订阅者异常不影响移动 */ }
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** 订阅常驻面状态(停靠/停放/降级),返回取消函数。 */
|
|
283
|
+
function subscribeSurface(fn) {
|
|
284
|
+
surfaceState.listeners.add(fn)
|
|
285
|
+
return function () { surfaceState.listeners.delete(fn) }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function surfaceSnapshot() { return snapshot() }
|
|
289
|
+
|
|
290
|
+
/** 停放策略:offscreen(默认)或 behind。切换时若当前处于停放态,立即重新摆放。 */
|
|
291
|
+
function setParkStrategy(strategy) {
|
|
292
|
+
if (strategy !== 'offscreen' && strategy !== 'behind') return
|
|
293
|
+
if (surfaceState.parkStrategy === strategy) return
|
|
294
|
+
surfaceState.parkStrategy = strategy
|
|
295
|
+
if (surfaceState.frame !== null && surfaceState.owner === null) applyParkStyle()
|
|
296
|
+
notify()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function getParkStrategy() { return surfaceState.parkStrategy }
|
|
300
|
+
|
|
301
|
+
function parkHasFixedSize() {
|
|
302
|
+
// 保留最后停靠尺寸:后台的 VS Code 布局不变,回到前台无需重排。
|
|
303
|
+
var rect = surfaceState.lastRect
|
|
304
|
+
if (rect === null) return
|
|
305
|
+
surfaceState.park.style.width = Math.max(1, Math.round(rect.width)) + 'px'
|
|
306
|
+
surfaceState.park.style.height = Math.max(1, Math.round(rect.height)) + 'px'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function applyParkStyle() {
|
|
310
|
+
if (surfaceState.park === null) return
|
|
311
|
+
parkHasFixedSize()
|
|
312
|
+
if (surfaceState.parkStrategy === 'behind' && surfaceState.lastRect !== null) {
|
|
313
|
+
// 原位、可视但不被点击:压在面板(z-index 10)之下 → 不会被判定为"不可见 iframe"。
|
|
314
|
+
surfaceState.park.style.left = Math.round(surfaceState.lastRect.left) + 'px'
|
|
315
|
+
surfaceState.park.style.top = Math.round(surfaceState.lastRect.top) + 'px'
|
|
316
|
+
surfaceState.park.style.visibility = 'visible'
|
|
317
|
+
surfaceState.park.style.zIndex = '0'
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
surfaceState.park.style.left = PARK_OFFSCREEN_LEFT + 'px'
|
|
321
|
+
surfaceState.park.style.top = '0px'
|
|
322
|
+
surfaceState.park.style.visibility = 'hidden'
|
|
323
|
+
surfaceState.park.style.zIndex = '0'
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** 停放态:不可交互、不进无障碍树、不抢焦点。 */
|
|
327
|
+
function markParked() {
|
|
328
|
+
if (surfaceState.frame === null) return
|
|
329
|
+
surfaceState.frame.setAttribute('inert', '')
|
|
330
|
+
surfaceState.frame.setAttribute('aria-hidden', 'true')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function markDocked() {
|
|
334
|
+
if (surfaceState.frame === null) return
|
|
335
|
+
surfaceState.frame.removeAttribute('inert')
|
|
336
|
+
surfaceState.frame.removeAttribute('aria-hidden')
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function createSurface() {
|
|
340
|
+
var park = document.createElement('div')
|
|
341
|
+
park.className = 'dshcs-park'
|
|
342
|
+
park.setAttribute('data-dshcs-park', '')
|
|
343
|
+
park.setAttribute('aria-hidden', 'true')
|
|
344
|
+
var frame = document.createElement('iframe')
|
|
345
|
+
frame.className = 'dshcs-frame'
|
|
346
|
+
frame.setAttribute('title', 'code-server')
|
|
347
|
+
frame.setAttribute('allow', 'clipboard-read; clipboard-write')
|
|
348
|
+
frame.setAttribute('data-dshcs-resident', '')
|
|
349
|
+
frame.src = 'about:blank'
|
|
350
|
+
park.appendChild(frame)
|
|
351
|
+
document.body.appendChild(park)
|
|
352
|
+
surfaceState.park = park
|
|
353
|
+
surfaceState.frame = frame
|
|
354
|
+
surfaceState.currentSrc = 'about:blank'
|
|
355
|
+
surfaceState.owner = null
|
|
356
|
+
applyParkStyle()
|
|
357
|
+
markParked()
|
|
358
|
+
return frame
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** serve=dsh 时 iframe 与 DSH 同源:此时不再挂 sandbox(同源 + allow-same-origin 可被 frame 自摘);
|
|
362
|
+
* loopback 跨源,sandbox 是真防护。属性变化会触发一次导航(模式切换本就该重载)。 */
|
|
363
|
+
function applyFrameAttrs(sameOrigin) {
|
|
364
|
+
var frame = surfaceState.frame
|
|
365
|
+
if (frame === null) return
|
|
366
|
+
var changed = surfaceState.sameOrigin !== sameOrigin
|
|
367
|
+
if (!changed && frame.hasAttribute('data-dshcs-attrs')) return
|
|
368
|
+
if (sameOrigin === true) frame.removeAttribute('sandbox')
|
|
369
|
+
else if (sameOrigin === false) {
|
|
370
|
+
frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-modals allow-popups '
|
|
371
|
+
+ 'allow-pointer-lock allow-clipboard-read allow-clipboard-write')
|
|
372
|
+
}
|
|
373
|
+
frame.setAttribute('data-dshcs-attrs', sameOrigin === true ? 'same-origin' : 'cross-origin')
|
|
374
|
+
surfaceState.sameOrigin = sameOrigin
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** 确保常驻面存在并指向 src(不改变停靠状态)。用于后台预热与首次停靠前的准备。 */
|
|
378
|
+
function ensureSurface(options) {
|
|
379
|
+
var opts = options || {}
|
|
380
|
+
if (surfaceState.frame === null) createSurface()
|
|
381
|
+
if (typeof opts.sameOrigin === 'boolean') applyFrameAttrs(opts.sameOrigin)
|
|
382
|
+
if (typeof opts.src === 'string' && opts.src !== '' && opts.src !== surfaceState.currentSrc) setSurfaceSrc(opts.src)
|
|
383
|
+
return surfaceState.frame
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** 显式导航(工作区/端口变化时唯一的正常重载入口)。 */
|
|
387
|
+
function setSurfaceSrc(src) {
|
|
388
|
+
if (surfaceState.frame === null || typeof src !== 'string' || src === '' || src === surfaceState.currentSrc) return
|
|
389
|
+
surfaceState.currentSrc = src
|
|
390
|
+
surfaceState.frame.src = src
|
|
391
|
+
notify()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** 陈旧合成表面修复:**同一 JS 任务内**关/开一次布局,逼浏览器重建 iframe 的合成表面。
|
|
395
|
+
* 不重载文档、不改变内部状态(实测:VS Code 布局与"欢迎"页状态保持),也不产生可见闪烁。 */
|
|
396
|
+
function nudgeRepaint() {
|
|
397
|
+
var frame = surfaceState.frame
|
|
398
|
+
if (surfaceState.nudgeEnabled !== true || frame === null || frame.isConnected !== true) return false
|
|
399
|
+
var prev = frame.style.display
|
|
400
|
+
frame.style.display = 'none'
|
|
401
|
+
void frame.offsetHeight // 强制重排:丢弃陈旧表面
|
|
402
|
+
frame.style.display = prev
|
|
403
|
+
surfaceState.nudgeCount += 1
|
|
404
|
+
surfaceState.lastNudgeAt = Date.now()
|
|
405
|
+
return true
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** 排障用:临时关闭修复,做 A/B 对照(默认开启)。 */
|
|
409
|
+
function setNudgeEnabled(enabled) {
|
|
410
|
+
surfaceState.nudgeEnabled = enabled !== false
|
|
411
|
+
return surfaceState.nudgeEnabled
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function moveInto(target) {
|
|
415
|
+
if (surfaceState.frame === null) return
|
|
416
|
+
if (supportsMoveBefore) {
|
|
417
|
+
try {
|
|
418
|
+
target.moveBefore(surfaceState.frame, null)
|
|
419
|
+
return
|
|
420
|
+
} catch (error) {
|
|
421
|
+
// 实测:源容器已被摘出文档时(React 先删 DOM、后跑 passive effect cleanup),
|
|
422
|
+
// moveBefore 抛 HierarchyRequestError("invalid hierarchy")。此时退回 appendChild:
|
|
423
|
+
// 会重载一次,但**绝不丢帧**(绝不能把 iframe 留在已脱离文档的宿主里)。
|
|
424
|
+
surfaceState.degraded = true
|
|
425
|
+
surfaceState.lastMoveError = (error && error.name ? error.name + ': ' : '') + (error && error.message ? error.message : String(error))
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
surfaceState.degraded = true
|
|
429
|
+
}
|
|
430
|
+
target.appendChild(surfaceState.frame)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** 把常驻面停靠到 host 容器(标签 body 里的占位 div)。 */
|
|
434
|
+
function dockInto(host, owner) {
|
|
435
|
+
if (surfaceState.frame === null || host == null) return
|
|
436
|
+
var wasParked = surfaceState.owner === null
|
|
437
|
+
var rect = host.getBoundingClientRect()
|
|
438
|
+
if (rect.width >= 1 && rect.height >= 1) {
|
|
439
|
+
surfaceState.lastRect = { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
|
|
440
|
+
}
|
|
441
|
+
surfaceState.owner = owner === undefined ? null : owner
|
|
442
|
+
markDocked()
|
|
443
|
+
surfaceState.park.setAttribute('aria-hidden', 'true')
|
|
444
|
+
moveInto(host)
|
|
445
|
+
// 从停放回到停靠:补一次重绘唤醒(见文件头"另一个实测坑")。
|
|
446
|
+
if (wasParked) nudgeRepaint()
|
|
447
|
+
notify()
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** 把常驻面移回停放区;仅当 owner 匹配(或未指定)时生效,避免非当前停靠者误停放。 */
|
|
451
|
+
function parkSurface(owner) {
|
|
452
|
+
if (surfaceState.frame === null) return
|
|
453
|
+
if (owner !== undefined && surfaceState.owner !== owner) return
|
|
454
|
+
surfaceState.owner = null
|
|
455
|
+
applyParkStyle()
|
|
456
|
+
markParked()
|
|
457
|
+
surfaceState.park.setAttribute('aria-hidden', 'true')
|
|
458
|
+
moveInto(surfaceState.park)
|
|
459
|
+
notify()
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** 后台预热:建面 + 指向 src 并停在停放区(keepResident=true 时由宿主侧状态驱动)。
|
|
463
|
+
* 已经停靠着(用户正在看 IDE)时只更新 src,绝不把面拽走。 */
|
|
464
|
+
function preloadSurface(options) {
|
|
465
|
+
var opts = options || {}
|
|
466
|
+
ensureSurface(opts)
|
|
467
|
+
if (surfaceState.owner === null) {
|
|
468
|
+
if (typeof opts.src === 'string' && opts.src !== '') surfaceState.preloaded = true
|
|
469
|
+
parkSurface()
|
|
470
|
+
}
|
|
471
|
+
return surfaceState.frame
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** 仅供排障/测试:销毁并复位。 */
|
|
475
|
+
function destroySurface() {
|
|
476
|
+
if (surfaceState.park !== null && surfaceState.park.parentNode !== null) surfaceState.park.parentNode.removeChild(surfaceState.park)
|
|
477
|
+
surfaceState.park = null
|
|
478
|
+
surfaceState.frame = null
|
|
479
|
+
surfaceState.currentSrc = null
|
|
480
|
+
surfaceState.sameOrigin = null
|
|
481
|
+
surfaceState.owner = null
|
|
482
|
+
surfaceState.lastRect = null
|
|
483
|
+
surfaceState.degraded = false
|
|
484
|
+
surfaceState.preloaded = false
|
|
485
|
+
surfaceState.nudgeCount = 0
|
|
486
|
+
surfaceState.lastNudgeAt = 0
|
|
487
|
+
notify()
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 排障/应急句柄:控制台可直接查看状态、切停放策略(offscreen ↔ behind)、手动停靠/停放。
|
|
491
|
+
if (typeof window !== 'undefined') {
|
|
492
|
+
window.__dshcsSurface = {
|
|
493
|
+
snapshot: snapshot,
|
|
494
|
+
setParkStrategy: setParkStrategy,
|
|
495
|
+
getParkStrategy: getParkStrategy,
|
|
496
|
+
preload: preloadSurface,
|
|
497
|
+
dock: dockInto,
|
|
498
|
+
park: parkSurface,
|
|
499
|
+
nudge: nudgeRepaint,
|
|
500
|
+
setNudgeEnabled: setNudgeEnabled,
|
|
501
|
+
destroy: destroySurface,
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ================================ 右侧栏全屏动作 —— 原 src/sidebar-mode.js(0.3.58 起内联:客户端不再构建) ================================
|
|
506
|
+
// —— 把右侧栏切到"全屏(铺满窗口)"这一个动作(打开 Code Server 标签时自动做)
|
|
507
|
+
//
|
|
508
|
+
// 为什么是"点它自己的按钮",而不是调服务:
|
|
509
|
+
// DSH 只把"展开/收起"开放给插件(ctx.sidebarRight.isExpanded / toggleExpanded);
|
|
510
|
+
// push ⟷ fullscreen 这个"模式"记在 ui-sidebar-right 自己的 store 里(actions.setMode),
|
|
511
|
+
// 只发给它的 seat 内部组件,插件拿不到。ctx.layout.openRightbar(track, fullscreen) 不是控制面 ——
|
|
512
|
+
// 它是 seat 用来"汇报"presentation 的通道(源码注释:the occupant reports it; nothing else writes it),
|
|
513
|
+
// 插件去写只会被 seat 的下一次提交覆盖。
|
|
514
|
+
// 所以这里复刻用户的动作本身:点面板 chrome 上的那颗「全屏」按钮 —— 与用户手点完全同一条路径
|
|
515
|
+
// (含窄视口下的连带处理),上游改模式语义时本插件自动跟随。
|
|
516
|
+
//
|
|
517
|
+
// 作用域:只在本插件 body 所属的那个面板内查找(closest('[data-sidebar-right-panel]')),
|
|
518
|
+
// 因此多会话/多面板并存时不会误点别人的按钮;找不到就保持现状并回报原因,由调用方记一次日志。
|
|
519
|
+
|
|
520
|
+
/** 面板根节点:ui-sidebar-right 的 SidebarPanel 挂的属性。 */
|
|
521
|
+
var PANEL_SELECTOR = '[data-sidebar-right-panel]'
|
|
522
|
+
/** chrome 上的模式按钮,属性值是"点它之后的目标模式":非全屏时即 "fullscreen"。 */
|
|
523
|
+
var FULLSCREEN_CONTROL = '[data-sidebar-right-mode="fullscreen"]'
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* 把本插件所在面板切到全屏模式(等价用户点「全屏」)。已全屏时该按钮不存在 → 'no-control'。
|
|
527
|
+
* 纯查询/点击失败一律回报原因,不抛错:这是"锦上添花"的动作,绝不能打断右侧栏渲染。
|
|
528
|
+
* @param rootEl - 本插件 body 的根 DOM 节点。
|
|
529
|
+
* @returns 'clicked' | 'no-root' | 'no-panel' | 'no-control' | 'lookup-failed' | 'click-failed'
|
|
530
|
+
*/
|
|
531
|
+
function requestFullscreenPanel(rootEl) {
|
|
532
|
+
if (rootEl == null || typeof rootEl.closest !== 'function') return 'no-root'
|
|
533
|
+
var panel
|
|
534
|
+
var control
|
|
535
|
+
try {
|
|
536
|
+
panel = rootEl.closest(PANEL_SELECTOR)
|
|
537
|
+
if (panel == null || typeof panel.querySelector !== 'function') return 'no-panel'
|
|
538
|
+
control = panel.querySelector(FULLSCREEN_CONTROL)
|
|
539
|
+
} catch (e) { return 'lookup-failed' }
|
|
540
|
+
if (control == null || typeof control.click !== 'function') return 'no-control'
|
|
541
|
+
// 点的是 React 组件上的 onClick:事件冒泡到 root 容器后由 React 委托分发。
|
|
542
|
+
// 上游 handler 抛错也不该顺着 layout effect 冒进 React 渲染流程。
|
|
543
|
+
try { control.click() } catch (e) { return 'click-failed' }
|
|
544
|
+
return 'clicked'
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ================================ 认领类型(与 host 同源,见 scripts/test-client-entry.mjs 的一致性守卫) —— 原 lib/claim-types.js(0.3.58 起内联:客户端不再构建) ================================
|
|
548
|
+
/**
|
|
549
|
+
* dsh-code-server — 认领类型策略(0.2.11)
|
|
550
|
+
*
|
|
551
|
+
* 决定"哪些文件地址由本插件认领"(即交给 VS Code 打开),**只按文件类型判断,不再区分作用域**:
|
|
552
|
+
* `dsh-resource://file/session/…` 与 `…/file/absolute/…` 一视同仁,未认领的落回 DSH 自带预览
|
|
553
|
+
* (`ui-sidebar-documentpreview`,优先级 fallback),它内部按扩展名分派 markdown / html / 图片 / PDF /
|
|
554
|
+
* 代码高亮 / 纯文本。
|
|
555
|
+
*
|
|
556
|
+
* 文本语法(用户可在设置卡里改,分号分隔,`,`/空白/换行也认):
|
|
557
|
+
* * 其余类型也认领(兜底)
|
|
558
|
+
* py 认领 .py(写 `py`、`.py`、`*.py` 等价;大小写不敏感)
|
|
559
|
+
* !md 不认领 .md(排除优先于认领)
|
|
560
|
+
* 空文本 = 不认领任何文件类型(只保留页面 tab,文件点击全部落回 DSH 预览)。
|
|
561
|
+
*
|
|
562
|
+
* 本模块是 host(`lib/index.js` 的 Config 默认值)与 client(`lib/client.js` 内联的那份副本)的
|
|
563
|
+
* **同一份** 事实来源,随 npm 包一起发布(见 package.json 的 files),避免两边默认值漂移。
|
|
564
|
+
* 纯字符串逻辑,不碰文件系统 → 可离线单测(scripts/test-claim-types.mjs)。
|
|
565
|
+
*/
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* 默认策略(0.3.51 起三组):
|
|
569
|
+
* ① **DSH 自带预览渲染得好的类型留给它**(markdown / html / 图片 / PDF);
|
|
570
|
+
* ② **可执行文件与二进制产物不认领**(用户 2026-09-18 要求):它们不是文本,进编辑器只会看到
|
|
571
|
+
* "二进制/乱码",没有编辑价值;
|
|
572
|
+
* ③ **Office / 版式文档不认领**(同一要求):都是 zip/OLE 二进制容器,同理。
|
|
573
|
+
* 其余(代码、json/yaml/toml、txt、日志、无扩展名、其它未知扩展名)仍全部进 IDE —— 未认领的类型
|
|
574
|
+
* 没有"没渲染器"的风险(DSH 预览对任何扩展名都有兜底),反之未知扩展名进 IDE 更实用。
|
|
575
|
+
*
|
|
576
|
+
* 想放开某一组(例如仍希望 Office 文档进 IDE):把设置卡的「认领类型」改回
|
|
577
|
+
* `*;!md;!markdown;!html;!htm;!png;!jpg;!jpeg;!gif;!webp;!bmp;!ico;!svg;!pdf` 即可。
|
|
578
|
+
*/
|
|
579
|
+
|
|
580
|
+
/** ① 预览友好的类型(DSH 自带预览比"进 IDE 当文本看"更好)。 */
|
|
581
|
+
const PREVIEW_FRIENDLY_EXTENSIONS = [
|
|
582
|
+
'md', 'markdown', 'html', 'htm', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'ico', 'svg', 'pdf',
|
|
583
|
+
]
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* ② 可执行文件与二进制产物。
|
|
587
|
+
* **文本形态的脚本刻意不在其中**(bat/cmd/ps1/sh/py/js…):它们是可编辑的文本,继续进 IDE。
|
|
588
|
+
* 判据统一是"进编辑器有没有意义",而不是"能不能被执行"。
|
|
589
|
+
*/
|
|
590
|
+
const EXECUTABLE_EXTENSIONS = [
|
|
591
|
+
// Windows:可执行程序 / 安装包 / 系统与驱动组件
|
|
592
|
+
'exe', 'com', 'msi', 'msix', 'msixbundle', 'appx', 'appxbundle', 'dll', 'sys', 'scr', 'cpl', 'ocx', 'drv', 'efi', 'mui',
|
|
593
|
+
// 编译与中间产物
|
|
594
|
+
'obj', 'o', 'a', 'lib', 'pdb', 'class', 'jar', 'pyc', 'pyo', 'wasm', 'node',
|
|
595
|
+
// POSIX:动态库 / 可执行 / 内核模块
|
|
596
|
+
'so', 'dylib', 'ko', 'elf', 'bin', 'out',
|
|
597
|
+
// 打包体(读不了也装不上)
|
|
598
|
+
'apk', 'ipa', 'deb', 'rpm', 'dmg', 'iso', 'img', 'cab',
|
|
599
|
+
]
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* ③ Office / 版式文档(zip / OLE 二进制容器)。
|
|
603
|
+
* `csv` / `tsv` **不在此列** —— 它们是纯文本,进 IDE 反而有用(只是 Excel 也能开)。
|
|
604
|
+
*/
|
|
605
|
+
const OFFICE_EXTENSIONS = [
|
|
606
|
+
// Word
|
|
607
|
+
'doc', 'docx', 'docm', 'dot', 'dotx', 'dotm', 'docb', 'rtf', 'odt',
|
|
608
|
+
// Excel
|
|
609
|
+
'xls', 'xlsx', 'xlsm', 'xlsb', 'xlt', 'xltx', 'xltm', 'xla', 'xlam', 'ods',
|
|
610
|
+
// PowerPoint
|
|
611
|
+
'ppt', 'pptx', 'pptm', 'pot', 'potx', 'potm', 'pps', 'ppsx', 'ppam', 'odp',
|
|
612
|
+
// Visio / OneNote / Project / Publisher / 版式文档
|
|
613
|
+
'vsd', 'vsdx', 'vssx', 'vstx', 'vsdm', 'vssm', 'vstm', 'one', 'onetoc2', 'mpt', 'mpp', 'pub', 'msg', 'xps', 'oxps', 'odg',
|
|
614
|
+
]
|
|
615
|
+
|
|
616
|
+
/** 默认策略文本:`*` + 三组排除(同一份事实来源,host 的 Config 默认值与 client 的 canOpen 共用)。 */
|
|
617
|
+
const DEFAULT_CLAIM_EXTENSIONS = ['*']
|
|
618
|
+
.concat(PREVIEW_FRIENDLY_EXTENSIONS)
|
|
619
|
+
.concat(EXECUTABLE_EXTENSIONS)
|
|
620
|
+
.concat(OFFICE_EXTENSIONS)
|
|
621
|
+
.map(function (token, index) { return index === 0 ? token : '!' + token })
|
|
622
|
+
.join(';')
|
|
623
|
+
|
|
624
|
+
/** 分隔符:分号为主,逗号/空白/换行也接受(用户手抄时不该因为标点被坑)。 */
|
|
625
|
+
const SEPARATORS = /[;,\s]+/
|
|
626
|
+
|
|
627
|
+
/** 切词:`!` 可以和后面的扩展名隔着空白写(`! .md` ≡ `!.md`),其余按分隔符切。 */
|
|
628
|
+
function tokenize(text) {
|
|
629
|
+
return String(text).replace(/!\s+/g, '!').split(SEPARATORS)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** 单个 token 归一化:去 `*.`/`.` 前缀、去首尾空白、小写;`*`/`!x` 形式保留语义。 */
|
|
633
|
+
function normalizeToken(raw) {
|
|
634
|
+
var token = typeof raw === 'string' ? raw.trim() : ''
|
|
635
|
+
if (token === '') return ''
|
|
636
|
+
var deny = token.charAt(0) === '!'
|
|
637
|
+
var body = deny ? token.slice(1).trim() : token
|
|
638
|
+
if (body === '*') return deny ? '!*' : '*'
|
|
639
|
+
if (body.slice(0, 2) === '*.') body = body.slice(2)
|
|
640
|
+
else if (body.charAt(0) === '.') body = body.slice(1)
|
|
641
|
+
body = body.toLowerCase()
|
|
642
|
+
if (body === '') return ''
|
|
643
|
+
return deny ? '!' + body : body
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* 归一化成一串规范文本(保存进设置用):去空、去重、保持"先出现的先写"。
|
|
648
|
+
* @param text - 用户输入(任意标点)。
|
|
649
|
+
* @returns 规范文本;输入非字符串时返回 ''。
|
|
650
|
+
*/
|
|
651
|
+
function normalizeClaimExtensions(text) {
|
|
652
|
+
if (typeof text !== 'string') return ''
|
|
653
|
+
var parts = tokenize(text)
|
|
654
|
+
var seen = []
|
|
655
|
+
for (var i = 0; i < parts.length; i += 1) {
|
|
656
|
+
var token = normalizeToken(parts[i])
|
|
657
|
+
if (token === '' || token === '!*') continue // `!*` 无意义:排除"其余"等于清空,不猜意图,直接忽略
|
|
658
|
+
if (seen.indexOf(token) === -1) seen.push(token)
|
|
659
|
+
}
|
|
660
|
+
return seen.join(';')
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* 解析策略文本。
|
|
665
|
+
* @param text - 归一化前或后的文本都接受。
|
|
666
|
+
* @returns {{all: boolean, allow: string[], deny: string[]}} all = `*` 出现(其余类型也认领)。
|
|
667
|
+
*/
|
|
668
|
+
function parseClaimPolicy(text) {
|
|
669
|
+
var normalized = normalizeClaimExtensions(text)
|
|
670
|
+
var tokens = normalized === '' ? [] : normalized.split(';')
|
|
671
|
+
var all = false
|
|
672
|
+
var allow = []
|
|
673
|
+
var deny = []
|
|
674
|
+
for (var i = 0; i < tokens.length; i += 1) {
|
|
675
|
+
var token = tokens[i]
|
|
676
|
+
if (token === '*') { all = true; continue }
|
|
677
|
+
if (token.charAt(0) === '!') deny.push(token.slice(1))
|
|
678
|
+
else allow.push(token)
|
|
679
|
+
}
|
|
680
|
+
return { all: all, allow: allow, deny: deny }
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** 路径的扩展名(小写、不含点);无扩展名(含 dotfile、目录、空路径)返回 ''。 */
|
|
684
|
+
function extensionOfPath(path) {
|
|
685
|
+
if (typeof path !== 'string' || path === '') return ''
|
|
686
|
+
var name = path.replace(/[/\\]+$/, '')
|
|
687
|
+
var at = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'))
|
|
688
|
+
if (at !== -1) name = name.slice(at + 1)
|
|
689
|
+
var dot = name.lastIndexOf('.')
|
|
690
|
+
// dot <= 0:`Makefile` / `README`(无扩展名)、`.gitignore`(dotfile)都按"无扩展名"处理
|
|
691
|
+
if (dot <= 0) return ''
|
|
692
|
+
return name.slice(dot + 1).toLowerCase()
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* 是否认领这个路径。排除优先,其次显式认领,最后看 `*`。
|
|
697
|
+
* @param path - 地址里的路径(可为相对路径)。
|
|
698
|
+
* @param policy - parseClaimPolicy 的结果。
|
|
699
|
+
* @returns 认领返回 true。
|
|
700
|
+
*/
|
|
701
|
+
function claimsPath(path, policy) {
|
|
702
|
+
if (policy == null) return false
|
|
703
|
+
var ext = extensionOfPath(path)
|
|
704
|
+
if (ext !== '' && policy.deny.indexOf(ext) !== -1) return false
|
|
705
|
+
if (ext !== '' && policy.allow.indexOf(ext) !== -1) return true
|
|
706
|
+
return policy.all === true
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* 是否认领这个文件地址(供 tab 类型的 canOpen 用)。
|
|
711
|
+
* @param parsed - parseFileAddress 的结果(或 null)。
|
|
712
|
+
* @param policy - parseClaimPolicy 的结果。
|
|
713
|
+
* @returns 认领返回 true。
|
|
714
|
+
*/
|
|
715
|
+
function claimsAddress(parsed, policy) {
|
|
716
|
+
if (parsed === null || parsed == null) return false
|
|
717
|
+
return claimsPath(parsed.path, policy)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** 给设置卡/日志用的一句话摘要(不参与判定)。 */
|
|
721
|
+
function describeClaimPolicy(text) {
|
|
722
|
+
var policy = parseClaimPolicy(text)
|
|
723
|
+
var parts = []
|
|
724
|
+
if (policy.all) parts.push('其余类型全部认领')
|
|
725
|
+
if (policy.allow.length > 0) parts.push('指定认领 ' + policy.allow.length + ' 项')
|
|
726
|
+
if (policy.deny.length > 0) {
|
|
727
|
+
// 把"排除了哪几组"说出来:默认值里可执行文件与 Office 各占几十项,只报总数看不出重点。
|
|
728
|
+
var groups = []
|
|
729
|
+
if (intersects(policy.deny, EXECUTABLE_EXTENSIONS)) groups.push('可执行文件')
|
|
730
|
+
if (intersects(policy.deny, OFFICE_EXTENSIONS)) groups.push('Office 文档')
|
|
731
|
+
parts.push('排除 ' + policy.deny.length + ' 项' + (groups.length > 0 ? '(含' + groups.join('、') + ')' : ''))
|
|
732
|
+
}
|
|
733
|
+
if (parts.length === 0) return '不认领任何文件(只有页面标签)'
|
|
734
|
+
return parts.join(' · ')
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** 两个列表有没有交集(摘要用;两侧都可能很长,用 Set 查)。 */
|
|
738
|
+
function intersects(list, group) {
|
|
739
|
+
var set = new Set(group)
|
|
740
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
741
|
+
if (set.has(list[i])) return true
|
|
742
|
+
}
|
|
743
|
+
return false
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ================================ 插件主体 —— 原 src/factory.js(0.3.58 起内联:客户端不再构建) ================================
|
|
747
|
+
// dsh-code-server — 客户端半部(右侧栏标签内的常驻 IDE 面;**只支持带右侧栏的 DSH**)
|
|
748
|
+
// 形态:window.__ModuleLoader__.load({id,factory}) 的经典脚本;factory 内静态 require('react')
|
|
749
|
+
// (DSH 冻结模块表)。0.3.58 起**不再构建** —— 这个文件就是源码,改它即改客户端。
|
|
750
|
+
//
|
|
751
|
+
// 数据通道:同源 fetch DSH Connection 的共享 /api(status / start / stop / setup / open-file / ui-mode;
|
|
752
|
+
// web 走 webServer 的 /api 前缀,desktop 走 IPC 帧管道 → 插件不依赖 webServer)。
|
|
753
|
+
//
|
|
754
|
+
// UI 载体只有右侧栏标签(需要 sidebarRightTabs / sidebarRight 服务,DSH ≥ 0.1.5-alpha.1):
|
|
755
|
+
// 一段 ctx.sidebarRightTabs.register({ id, kind, priority, patterns, canOpen, title, guide })
|
|
756
|
+
// → guide 页入口框;并通过 patterns 认领 `dsh-resource://file/**` 文件地址,成为官方 openFile
|
|
757
|
+
// (产物 chip / "交付"卡片预览 / 正文提及)与任何 openResource(fileAddress) 的落点。
|
|
758
|
+
// 二段 ctx.slots.register({ name:'sidebar.right.pane.tab', key:id }, CodeServerBody)
|
|
759
|
+
// → tab 内渲染常驻 iframe;body 从 navigation.address 解析文件并让 workbench 定位。
|
|
760
|
+
//
|
|
761
|
+
// 探测不到右侧栏服务(旧版 DSH)时:除「设置 → 插件 → Code Server」的一条提示外不注册任何 UI、
|
|
762
|
+
// 不预热 IDE,并上报 host /ui-mode 让它回收已自动预启动的实例。
|
|
763
|
+
|
|
764
|
+
// 工厂体:module/exports/require 由构建产物外层的 factory 参数提供(见 footer/banner)
|
|
765
|
+
let React = require('react')
|
|
766
|
+
|
|
767
|
+
// ---------- 模块级共享 store:同步 status/busy + UI 载体状态 ----------
|
|
768
|
+
var listeners = new Set()
|
|
769
|
+
// sidebarUi:'unknown'(未探测)| 'modern'(右侧栏服务就绪)| 'legacy'(旧版 DSH,只提示)
|
|
770
|
+
var state = { status: null, busy: false, sidebarUi: 'unknown', sidebarRegisterFailed: false }
|
|
771
|
+
function setState(patch) {
|
|
772
|
+
state = Object.assign({}, state, patch)
|
|
773
|
+
listeners.forEach(function (fn) { fn() })
|
|
774
|
+
}
|
|
775
|
+
function subscribe(fn) {
|
|
776
|
+
listeners.add(fn)
|
|
777
|
+
return function () { listeners.delete(fn) }
|
|
778
|
+
}
|
|
779
|
+
function getState() { return state }
|
|
780
|
+
function useStore() {
|
|
781
|
+
return React.useSyncExternalStore(subscribe, getState)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// ---------- /api/code-server API ----------
|
|
785
|
+
// 通道:DSH Connection 服务的共享 /api 通道(host 侧 ctx.connection.fetch.register)。
|
|
786
|
+
// web 由 webServer 的 /api 前缀承载,desktop 由 IPC 帧管道承载 → 客户端只写相对路径,
|
|
787
|
+
// 不再依赖 /code-server/* 这类 webServer 专有路由。
|
|
788
|
+
var API_PREFIX = '/api'
|
|
789
|
+
// code-server 官方图标(assets/favicon.svg 内联为 data URI)。
|
|
790
|
+
// 内联原因:插件不再注册任何 HTTP 路由(desktop 无 webServer),图标不依赖服务端路径。
|
|
791
|
+
var ICON_URL = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMTQ3IDE0NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8c3R5bGU+QG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgeyogeyBmaWxsOiB3aGl0ZTsgfX08L3N0eWxlPgogIDxwYXRoIGQ9Im00Mi40MjE0LDM5LjY1NWMtMjQuNDA1NywwIC00Mi4xNTU0LDEzLjE3MjEgLTQyLjE1NTQsMzMuODQ1YzAsMjAuNTgxNCAxOC40ODkyLDMzLjg0NSA0Mi4xNTU0LDMzLjg0NWMyMy42NjYyLDAgMzguMTgwMywtMTEuNjE3MSAzOC43MzQ5LC0yOC43MjI1bC0yMS4wNzc3LC0wLjQ1NzRjLTAuOTI0NCw5LjMzMDMgLTkuMTA1OSwxNS4xODQ1IC0xNy42NTcyLDE1LjE4NDVjLTExLjc0MDYsMCAtMjAuNDMwNiwtNy41OTIyIC0yMC40MzA2LC0xOS44NDk2YzAsLTEyLjI1NzQgOC42OSwtMTkuOTg2OCAyMC40MzA2LC0yMC4yMTU1YzguNTUxMywtMC4xODMgMTYuOTE3Nyw1Ljk0NTcgMTcuNDcyMywxNS4yNzZsMjEuMDc3NywtMC42NDAzYy0wLjQ2MjIsLTE2LjgzMTEgLTE0LjE0NDIsLTI4LjI2NTIgLTM4LjU1LC0yOC4yNjUyem00OC44NDQ2LDJsNTUuNDY4LDBsMCw2NC4wMzExbC01NS40NjgsMGwwLC02NC4wMzExeiIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg=='
|
|
792
|
+
async function api(path, body) {
|
|
793
|
+
var options = { method: body === undefined ? 'GET' : 'POST', headers: {} }
|
|
794
|
+
if (body !== undefined) {
|
|
795
|
+
options.headers['content-type'] = 'application/json'
|
|
796
|
+
options.body = JSON.stringify(body)
|
|
797
|
+
}
|
|
798
|
+
var res = await fetch(API_PREFIX + path, options)
|
|
799
|
+
var text = await res.text()
|
|
800
|
+
var data = null
|
|
801
|
+
try { data = text === '' ? null : JSON.parse(text) } catch (e) { data = null }
|
|
802
|
+
if (!res.ok) return { ok: false, error: 'HTTP ' + res.status + (data !== null && data.error !== undefined ? ': ' + data.error : '') }
|
|
803
|
+
return data !== null ? data : { ok: false, error: 'invalid JSON response' }
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// ---------- 标准 prop 里的会话/工作区快照 ----------
|
|
807
|
+
// 取哪条路、退到哪条路**全在本文件的工作区解析段**(纯函数):DSH ≤ 0.1.6-alpha.1 的
|
|
808
|
+
// `SessionListState.current` 在 0.1.6-alpha.2 被移出列表 store,会话作用域插槽改用标准 prop
|
|
809
|
+
// `sessionId` 标识当前会话 —— 只认旧形状会让 cwd 静默变成 undefined(IDE 以空工作区启动)。
|
|
810
|
+
/** 服务缺失时的占位选择器:**hook 调用次数必须恒定**(React 按位置对账),
|
|
811
|
+
* 否则"服务晚到"的那一次渲染会抛 "Rendered more hooks than during the previous render"。 */
|
|
812
|
+
function noopSelector() { return undefined }
|
|
813
|
+
function workspaceSnapshots(props) {
|
|
814
|
+
var useSessions = typeof props.useSessions === 'function' ? props.useSessions : noopSelector
|
|
815
|
+
var useWorkspaces = typeof props.useWorkspaces === 'function' ? props.useWorkspaces : noopSelector
|
|
816
|
+
return {
|
|
817
|
+
sessions: useSessions(function (s) { return s }),
|
|
818
|
+
workspaces: useWorkspaces(function (s) { return s }),
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** 构建 code-server 页面 URL(base + 实例标记 + ?folder=<cwd>,Windows 路径须为 /C:/ 形式)。
|
|
823
|
+
* cwd 为空时回退 status.cwd;两者皆无 → 只有实例标记。
|
|
824
|
+
* 末尾的 `s=` 是"实例标记"(pid/启动时间):IDE 重启后 URL 变化 → iframe 会自动重新导航。
|
|
825
|
+
* **没有它的话**,IDE 未就绪时加载到的错误页(503/连接失败)会一直粘在 iframe 上,
|
|
826
|
+
* 即使用户点了重启也不会重新加载(0.2.x 的老毛病)。 */
|
|
827
|
+
function buildPageUrl(status, cwd) {
|
|
828
|
+
if (status == null || typeof status.url !== 'string' || status.url === '') return null
|
|
829
|
+
var tag = status.pid != null ? String(status.pid) : (status.startedAt != null ? String(status.startedAt) : '0')
|
|
830
|
+
var url = status.url + '?s=' + encodeURIComponent(tag)
|
|
831
|
+
var dir = typeof cwd === 'string' && cwd !== '' ? cwd : (status != null && status.cwd != null ? status.cwd : null)
|
|
832
|
+
if (dir == null || dir === '') return url
|
|
833
|
+
var normalized = dir.replace(/\\/g, '/')
|
|
834
|
+
var folder = normalized
|
|
835
|
+
if (/^[A-Za-z]:\//.test(normalized)) folder = '/' + normalized
|
|
836
|
+
else if (normalized.charCodeAt(0) !== 47) folder = '/' + normalized
|
|
837
|
+
return url + '&folder=' + encodeURIComponent(folder)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/** 常驻 IDE 面组件(右侧栏标签 body 的唯一渲染者):只渲染"停靠位",iframe 由 surface.js 持有,
|
|
841
|
+
* 经 moveBefore 移入/移出 —— 切标签、收起侧栏、拖成浮动窗口都不再重载。
|
|
842
|
+
* reloadTick 变化 = 显式重载(切工作区/重试);owner 用于多停靠位仲裁:后停靠者接管,
|
|
843
|
+
* 非当前停靠者卸载时不得把面拽走。 */
|
|
844
|
+
function CodeServerSurface(props) {
|
|
845
|
+
var status = props.status
|
|
846
|
+
var pageUrl = props.pageUrl
|
|
847
|
+
var reloadTick = props.reloadTick
|
|
848
|
+
var owner = props.owner
|
|
849
|
+
var hostRef = React.useRef(null)
|
|
850
|
+
var running = status != null && status.ok === true && status.running === true
|
|
851
|
+
var starting = status != null && status.status === 'starting'
|
|
852
|
+
var errored = status != null && status.ok === false
|
|
853
|
+
// serve=dsh 时 iframe 与 DSH 同源(不挂 sandbox);loopback 跨源,sandbox 是真防护。
|
|
854
|
+
var sameOrigin = status != null && status.serve === 'dsh'
|
|
855
|
+
|
|
856
|
+
// 停靠必须在绘制前完成(useLayoutEffect)→ 不闪白;src 变化时只做"重新停靠 + 导航"。
|
|
857
|
+
React.useLayoutEffect(function () {
|
|
858
|
+
if (!running || pageUrl === null) {
|
|
859
|
+
// 未运行/启动中:本组件不再渲染停靠位(React 会摘掉 slot)。必须先把面收回停放区,
|
|
860
|
+
// 否则 iframe 会留在被摘除的宿主里(源容器脱离文档 → 下次 moveBefore 抛错并降级)。
|
|
861
|
+
parkSurface(owner)
|
|
862
|
+
return
|
|
863
|
+
}
|
|
864
|
+
ensureSurface({ src: pageUrl, sameOrigin: sameOrigin })
|
|
865
|
+
if (hostRef.current !== null) dockInto(hostRef.current, owner)
|
|
866
|
+
}, [running, pageUrl, sameOrigin, reloadTick, owner])
|
|
867
|
+
|
|
868
|
+
// 停放必须用 **layout** cleanup:React 在提交阶段先跑 layout effect 销毁、再摘除 DOM,
|
|
869
|
+
// 此时 iframe 仍连着文档 → moveBefore 成功(状态保持);若放到 passive cleanup,
|
|
870
|
+
// 宿主已被摘除,实测会抛 HierarchyRequestError 并把 iframe 丢掉。
|
|
871
|
+
React.useLayoutEffect(function () {
|
|
872
|
+
return function () { parkSurface(owner) }
|
|
873
|
+
}, [owner])
|
|
874
|
+
|
|
875
|
+
if (running && pageUrl !== null) {
|
|
876
|
+
return React.createElement('div', {
|
|
877
|
+
ref: hostRef,
|
|
878
|
+
className: 'dshcs-slot',
|
|
879
|
+
'data-code-server-slot': owner != null ? owner : 'code-server',
|
|
880
|
+
})
|
|
881
|
+
}
|
|
882
|
+
if (starting) {
|
|
883
|
+
// 启动中:先给占位与提示;常驻面由上一次的 cleanup 停在停放区,启动完成后自动停靠。
|
|
884
|
+
return React.createElement('div', { className: 'dshcs-loading' }, '正在启动 code-server…')
|
|
885
|
+
}
|
|
886
|
+
var errText = errored && status != null && status.error
|
|
887
|
+
? status.error
|
|
888
|
+
: 'code-server 未运行'
|
|
889
|
+
var modeHint = sameOrigin
|
|
890
|
+
? '当前以 DSH 同源路径 ' + ((status != null && status.url) || '/code-server/') + ' 提供(无独立端口)。'
|
|
891
|
+
: '当前以独立回环端口提供(端口 ' + (status != null && status.port != null ? status.port : '随机')
|
|
892
|
+
+ ' + 路径令牌;要在新标签页打开,复制设置卡里的完整地址 —— 缺了令牌那段会 404)。'
|
|
893
|
+
var residentHint = supportsMoveBefore
|
|
894
|
+
? '常驻面:可用(切标签/收起侧栏不重载)。'
|
|
895
|
+
: '常驻面:当前浏览器不支持 Element.moveBefore —— 切标签会整页重载(升级浏览器后自动可用)。'
|
|
896
|
+
return React.createElement('div', { className: 'dshcs-empty' },
|
|
897
|
+
React.createElement('div', { className: 'dshcs-emptybox' },
|
|
898
|
+
React.createElement('div', null, errored ? 'code-server 启动失败' : 'code-server 未运行'),
|
|
899
|
+
React.createElement('pre', { className: 'dshcs-error' }, errText),
|
|
900
|
+
React.createElement('p', { className: 'dshcs-hint' },
|
|
901
|
+
'VS Code 树随插件包内置、就地运行(无需全局安装、无需联网安装、无「安装环境」步骤);' +
|
|
902
|
+
'内部依赖与预编译原生模块由包管理器在安装插件时一并装好(无需 C++ 工具链)。' +
|
|
903
|
+
'若此处长期未运行,请查看 DSH host 日志里的 [code-server] 输出(或在设置卡片里切换「后台常驻」触发一次启动)。' +
|
|
904
|
+
modeHint + residentHint)
|
|
905
|
+
))
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/** 后台常驻预热(keepResident=true):宿主 running 后立即在停放区加载 workbench,
|
|
909
|
+
* 首次点开免等待。它不占任何插槽位置,也不把已停靠的面拽走。 */
|
|
910
|
+
function Resident(props) {
|
|
911
|
+
var store = useStore()
|
|
912
|
+
var status = store.status
|
|
913
|
+
var running = status != null && status.ok === true && status.running === true
|
|
914
|
+
var keep = status != null && status.keepResident === true
|
|
915
|
+
// 根作用域没有 sessionId:由 workspace.js 退到"最近活跃会话所属工作区"(拿不到就不带 folder,
|
|
916
|
+
// 打开面板时 body 会带着正确 `?folder=` 重新导航)。
|
|
917
|
+
var snapshots = workspaceSnapshots(props != null ? props : {})
|
|
918
|
+
var cwd = pickWorkspaceCwd({ sessions: snapshots.sessions, workspaces: snapshots.workspaces })
|
|
919
|
+
var url = running ? buildPageUrl(status, cwd) : null
|
|
920
|
+
React.useEffect(function () {
|
|
921
|
+
if (!keep || !running || url === null) return
|
|
922
|
+
preloadSurface({ src: url, sameOrigin: status != null && status.serve === 'dsh' })
|
|
923
|
+
}, [keep, running, url, status != null ? status.serve : null])
|
|
924
|
+
return null
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// ---------- 样式(主题变量 + 兜底值;只覆盖插件自身结构) ----------
|
|
928
|
+
var CSS =
|
|
929
|
+
// 常驻 IDE 面:iframe 由 surface.js 持有,借 moveBefore 在停靠位/停放区之间搬
|
|
930
|
+
'.dshcs-frame{display:block;position:absolute;inset:0;z-index:1;width:100%;height:100%;border:0;background:var(--dsw-alias-bg-base,#fff)}' +
|
|
931
|
+
'.dshcs-tabroot{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;background:var(--dsw-alias-bg-base,#fff)}' +
|
|
932
|
+
'.dshcs-slot{position:relative;display:block;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;background:var(--dsw-alias-bg-base,#fff)}' +
|
|
933
|
+
'.dshcs-park{position:fixed;left:-20000px;top:0;width:320px;height:200px;overflow:hidden;pointer-events:none;z-index:0;visibility:hidden;contain:strict}' +
|
|
934
|
+
// 空态/提示(未运行、启动中、旧版 DSH 提示)
|
|
935
|
+
'.dshcs-loading{position:absolute;inset:0;z-index:2;display:grid;place-items:center;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-tertiary,#7d8798);font-size:13px}' +
|
|
936
|
+
'.dshcs-empty{position:relative;z-index:2;flex:1;display:flex;align-items:center;justify-content:center;padding:44px 24px 24px}' +
|
|
937
|
+
'.dshcs-emptybox{max-width:560px;text-align:left;background:var(--dsw-alias-bg-layer-2,#f7f8fb);border:1px solid var(--dsw-alias-border-l2,#dfe3eb);border-radius:12px;padding:16px 18px}' +
|
|
938
|
+
'.dshcs-error{color:var(--dsw-alias-label-error,#d92d20);white-space:pre-wrap;word-break:break-all;font-family:ui-monospace,Consolas,monospace;font-size:12px;margin:8px 0 0}' +
|
|
939
|
+
'.dshcs-hint{color:var(--dsw-alias-label-tertiary,#7d8798);font-size:12px;margin:6px 0 0}' +
|
|
940
|
+
'.dshcs-legacy{color:var(--dsw-alias-label-warning,#b54708);font-size:12px;font-weight:600;margin:0 0 6px}'
|
|
941
|
+
var CSS_TAG = 'dsh-code-server/styles'
|
|
942
|
+
if (typeof document !== 'undefined' && document.querySelector('style[data-dshcs=' + JSON.stringify(CSS_TAG) + ']') === null) {
|
|
943
|
+
var tag = document.createElement('style')
|
|
944
|
+
tag.setAttribute('data-dshcs', CSS_TAG)
|
|
945
|
+
tag.textContent = CSS
|
|
946
|
+
document.head.appendChild(tag)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// ---------- 右侧栏标签(DSH ≥ 0.1.5-alpha.1:ctx.sidebarRightTabs / ctx.sidebarRight) ----------
|
|
950
|
+
// 二段式注册:类型定义进 sidebarRightTabs,body 进 keyed 插槽 sidebar.right.pane.tab。
|
|
951
|
+
// 服务始终不就绪(旧版 DSH)→ 判定 legacy:只留设置页提示(见 internalApply)。
|
|
952
|
+
var CS_KIND = 'code-server'
|
|
953
|
+
var CS_TAB_ID = 'dsh-code-server-app'
|
|
954
|
+
/** 本插件的**包名**,同时是客户端 bundle 的 loader id(banner 里 `load({id})` 那一个)。
|
|
955
|
+
* 插件页按包名派发配置区(`renderSlot('plugins.bundle.config', …, { entryKey: pkg.name })`),
|
|
956
|
+
* 所以这个键必须与安装进来的包名逐字相同 —— 0.3.50 起设置卡搬去插件页就是靠它认领。 */
|
|
957
|
+
var PKG_NAME = 'dsh-code-server-app'
|
|
958
|
+
/** 插件页配置区的 `summary` 视图(插件页只用得到 `page`;留着一句简介以防上游改用 summary)。 */
|
|
959
|
+
var CS_CONFIG_SUMMARY = '在右侧栏标签里运行 VS Code 网页版,跟随当前会话工作区;'
|
|
960
|
+
+ '认领类型、打开即全屏、后台常驻都在这里设。'
|
|
961
|
+
/** 旧版判定等待窗口:服务可能晚于本插件就绪,超过这个时间仍无服务即认定旧版 DSH。 */
|
|
962
|
+
var LEGACY_NOTICE_MS = 2500
|
|
963
|
+
/** 更久的宽限:到这里仍无服务才通知 host 停止预启动/回收实例(避免误杀慢启动的宿主)。 */
|
|
964
|
+
var LEGACY_REPORT_MS = 10000
|
|
965
|
+
/** 服务已在注册表、但 ctx.inject 迟迟不回调时的兜底注册延迟。 */
|
|
966
|
+
var SYNC_FALLBACK_MS = 1500
|
|
967
|
+
|
|
968
|
+
/** 侧栏入口图标:code-server 官方图标(内联 data URI,不依赖服务端路径)。 */
|
|
969
|
+
function CodeServerIcon(props) {
|
|
970
|
+
var size = props != null && typeof props.size === 'number' ? props.size : 16
|
|
971
|
+
return React.createElement('img', {
|
|
972
|
+
src: ICON_URL, alt: '', 'aria-hidden': true, draggable: false,
|
|
973
|
+
className: props != null ? props.className : undefined,
|
|
974
|
+
style: { width: size, height: size, display: 'block', objectFit: 'contain', WebkitUserDrag: 'none', userSelect: 'none' },
|
|
975
|
+
})
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/** 当前认领类型策略(host 快照里的 claimExtensions)。
|
|
979
|
+
* host 未应答时**不认领任何文件** —— 这时 IDE 也起不来,把点击交给 DSH 自带预览,
|
|
980
|
+
* 比"猜一个默认值"更诚实(host 一到,认领立即按设置生效)。 */
|
|
981
|
+
var NO_CLAIM_POLICY = parseClaimPolicy('')
|
|
982
|
+
function claimPolicy() {
|
|
983
|
+
var status = state.status
|
|
984
|
+
if (status == null || typeof status.claimExtensions !== 'string') return NO_CLAIM_POLICY
|
|
985
|
+
return parseClaimPolicy(status.claimExtensions)
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/** 默认认领策略的**分行展示文本**(设置卡提示块用,0.3.52)。
|
|
989
|
+
* 与 `DEFAULT_CLAIM_EXTENSIONS` 同源(都是 lib/claim-types.js 的三组常量),只是把 `;` 换成按组的换行,
|
|
990
|
+
* 便于阅读;解析器把换行与分号一视同仁,所以整段复制回输入框得到的策略与默认值逐字相同。 */
|
|
991
|
+
function claimDefaultLines() {
|
|
992
|
+
var lines = ['*']
|
|
993
|
+
var groups = [PREVIEW_FRIENDLY_EXTENSIONS, EXECUTABLE_EXTENSIONS, OFFICE_EXTENSIONS]
|
|
994
|
+
for (var i = 0; i < groups.length; i += 1) {
|
|
995
|
+
var tokens = []
|
|
996
|
+
for (var j = 0; j < groups[i].length; j += 1) tokens.push('!' + groups[i][j])
|
|
997
|
+
lines.push(tokens.join(';'))
|
|
998
|
+
}
|
|
999
|
+
return lines.join('\n')
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* DSH 侧**只保留一个** code-server 标签页(0.3.57,用户要求:"打开新文件不添加新的标签页")。
|
|
1004
|
+
*
|
|
1005
|
+
* 为什么需要插件自己收:官方的标签页身份就是**地址**本身 ——
|
|
1006
|
+
* `SidebarRightTabClaim.contentId` 注释写着 "Stable identity of the content, which is the address
|
|
1007
|
+
* itself. Two opens of the same address are the same tab"(同址幂等、异址必新开),
|
|
1008
|
+
* 而 `replaceTab` 只有**发起方**(产品自己的 openFile / openResource)能传,插件拿不到那个机会。
|
|
1009
|
+
* 所以插件在"新标签页的 body 挂载时"把同窗格里的旧标签页关掉 —— 结束时只剩一个,
|
|
1010
|
+
* 用户看到的就是"这**一个** code-server 页面在换内容"。
|
|
1011
|
+
*
|
|
1012
|
+
* 为什么敢关:常驻 IDE 面是 **surface.js 持有的单例 iframe**(在停靠位/停放区之间 moveBefore),
|
|
1013
|
+
* 标签页只是它的停靠宿主 —— 关掉旧标签不会重载、不会丢 IDE 状态(见 surface.js 文件头)。
|
|
1014
|
+
*
|
|
1015
|
+
* 两个刻意的边界:
|
|
1016
|
+
* · **只收同窗格的**:多窗格是用户主动切分的布局,官方自己也是"每窗格一份"的约定
|
|
1017
|
+
* (`SidebarRightTabDefinition.multiple` 的注释:"one page per kind in each pane")。
|
|
1018
|
+
* · **只有"首次可见"的那个收**:标签页恢复/激活顺序不可控,若每个可见的 body 都收别人,
|
|
1019
|
+
* 关掉一个会让下一个变可见 → 互相收(乒乓)。用 ref 钉住"每个挂载只收一次"。
|
|
1020
|
+
*/
|
|
1021
|
+
var csTabSeats = new Map()
|
|
1022
|
+
|
|
1023
|
+
/** 收掉同窗格里的其它本插件标签页(登记见 body 里的 layout effect)。 */
|
|
1024
|
+
function closeSiblingTabs(selfId, paneId) {
|
|
1025
|
+
var closed = 0
|
|
1026
|
+
csTabSeats.forEach(function (seat, id) {
|
|
1027
|
+
if (id === selfId) return
|
|
1028
|
+
// 两边窗格都已知且不同 ⇒ 放过(多窗格是用户主动切的布局);
|
|
1029
|
+
// 任一边窗格未知(老版 DSH 没给 panel)⇒ 按"同窗格"处理,宁可只留一个。
|
|
1030
|
+
if (paneId !== null && seat.pane !== null && seat.pane !== paneId) return
|
|
1031
|
+
try {
|
|
1032
|
+
seat.close()
|
|
1033
|
+
// 关成功就立刻注销座位:body 的 unmount cleanup 也会注销,但那是**稍后**(React 卸载时机),
|
|
1034
|
+
// 这期间若再来一次合并就会对同一个已关闭的标签页重复调 close(会抛,且日志噪音)。
|
|
1035
|
+
csTabSeats.delete(id)
|
|
1036
|
+
closed += 1
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
// 关不掉只是"多一个标签页",绝不能因此让 body 渲染失败
|
|
1039
|
+
console.warn('[code-server] 收起旧的 code-server 标签页失败:'
|
|
1040
|
+
+ (error != null && error.message != null ? error.message : error))
|
|
1041
|
+
}
|
|
1042
|
+
})
|
|
1043
|
+
return closed
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** 右侧栏 tab 的 body:面板里铺满常驻 IDE 面(iframe 由 surface.js 持有)。
|
|
1047
|
+
* 走共享 store 与 CodeServerSurface;挂载即让实例跟随当前会话工作区。
|
|
1048
|
+
* 文件 tab(navigation.address = `dsh-resource://file/…`)会让 workbench 定位到该文件;
|
|
1049
|
+
* 页面 tab(`sidebar://code-server`)只显示工作区 IDE。
|
|
1050
|
+
* 0.2.2 起 ui-dockkit 的"切走即卸载 body"不再导致重载:卸载只把面停放到停放区。
|
|
1051
|
+
* 0.3.57 起:新标签页挂载时会收掉同窗格里的旧 code-server 标签页(见 closeSiblingTabs)。 */
|
|
1052
|
+
function CodeServerBody(props) {
|
|
1053
|
+
var info = props.useTabInfo()
|
|
1054
|
+
var tab = info.tab
|
|
1055
|
+
var store = useStore()
|
|
1056
|
+
var status = store.status
|
|
1057
|
+
var navigation = tab.navigation
|
|
1058
|
+
var revision = navigation != null && typeof navigation.revision === 'number' ? navigation.revision : 0
|
|
1059
|
+
// 地址:文件 tab 可能来自会话树里的别的会话,故优先用地址里的 sessionId 对齐工作区;
|
|
1060
|
+
// 页面 tab 用**会话作用域标准 prop** `sessionId`(DSH ≥ 0.1.6-alpha.2 的当前会话信源,
|
|
1061
|
+
// 老版的 `useSessions().current` 已被上游移除 —— 详见本文件的工作区解析段)。
|
|
1062
|
+
var address = navigation != null && typeof navigation.address === 'string' ? navigation.address : ''
|
|
1063
|
+
var parsed = isPageAddress(address) ? null : parseFileAddress(address)
|
|
1064
|
+
var snapshots = workspaceSnapshots(props)
|
|
1065
|
+
var addressedSession = parsed != null && typeof parsed.sessionId === 'string' ? parsed.sessionId : undefined
|
|
1066
|
+
var cwd = pickWorkspaceCwd({
|
|
1067
|
+
sessions: snapshots.sessions,
|
|
1068
|
+
workspaces: snapshots.workspaces,
|
|
1069
|
+
sessionId: addressedSession !== undefined ? addressedSession : props.sessionId,
|
|
1070
|
+
})
|
|
1071
|
+
var cwdWarnedRef = React.useRef(false)
|
|
1072
|
+
var targetFile = resolveFilePath(parsed, cwd)
|
|
1073
|
+
var line = navigation != null && navigation.params != null && typeof navigation.params.line === 'number'
|
|
1074
|
+
? navigation.params.line : null
|
|
1075
|
+
var lastCwdRef = React.useRef(undefined)
|
|
1076
|
+
var [tick, setTick] = React.useState(0)
|
|
1077
|
+
// 打开即全屏(设置 fullscreenOnOpen,默认开):右侧栏里跑 IDE,只有铺满窗口才够用。
|
|
1078
|
+
var rootRef = React.useRef(null)
|
|
1079
|
+
var forcedFullscreenRef = React.useRef(false)
|
|
1080
|
+
var visible = info.tab != null && info.tab.visible === true
|
|
1081
|
+
var fullscreen = info.sidebar != null && info.sidebar.fullscreen === true
|
|
1082
|
+
// 单标签页(0.3.57):登记自己的"座位"(能关掉自己的那个函数 + 所在窗格),
|
|
1083
|
+
// 并在**首次可见**时把同窗格里的旧 code-server 标签页收掉 —— 见 closeSiblingTabs 的注释。
|
|
1084
|
+
var paneId = info.panel != null && info.panel.id !== undefined ? info.panel.id : null
|
|
1085
|
+
var actions = tab.actions != null ? tab.actions : null
|
|
1086
|
+
var tabId = tab.id
|
|
1087
|
+
var consolidatedRef = React.useRef(false)
|
|
1088
|
+
React.useLayoutEffect(function () {
|
|
1089
|
+
if (actions === null) return undefined
|
|
1090
|
+
csTabSeats.set(tabId, { pane: paneId, close: actions.close })
|
|
1091
|
+
return function () { csTabSeats.delete(tabId) }
|
|
1092
|
+
}, [tabId, paneId, actions])
|
|
1093
|
+
React.useLayoutEffect(function () {
|
|
1094
|
+
if (consolidatedRef.current || visible !== true || actions === null) return
|
|
1095
|
+
consolidatedRef.current = true
|
|
1096
|
+
closeSiblingTabs(tabId, paneId)
|
|
1097
|
+
}, [visible, tabId, paneId, actions])
|
|
1098
|
+
// status 未到达(host 尚未应答)时按"未知"处理 → 不抢跑;值到达后由依赖变化补一次。
|
|
1099
|
+
// 反过来(未知即当真)会在用户关掉设置、而 status 还在路上时误切一次全屏。
|
|
1100
|
+
var fullscreenOnOpen = status != null && status.fullscreenOnOpen !== false
|
|
1101
|
+
// 触发时机 = 本标签"变得可见"的那一刻(打开、切回、重新展开侧栏),每个可见周期只切一次:
|
|
1102
|
+
// 之后用户点「退出全屏」不会被抢回去(退出全屏→再切走切回才会重新切全屏)。
|
|
1103
|
+
// layout effect:与面板同一次提交,按钮已在 DOM 里 → 不会先画一帧 push 再跳全屏。
|
|
1104
|
+
React.useLayoutEffect(function () {
|
|
1105
|
+
if (!visible) { forcedFullscreenRef.current = false; return }
|
|
1106
|
+
if (!fullscreenOnOpen || fullscreen || forcedFullscreenRef.current) return
|
|
1107
|
+
forcedFullscreenRef.current = true
|
|
1108
|
+
var result = requestFullscreenPanel(rootRef.current)
|
|
1109
|
+
if (result !== 'clicked') {
|
|
1110
|
+
console.warn('[code-server] 未能自动切到全屏右侧栏(' + result + ');本次打开保持原模式')
|
|
1111
|
+
}
|
|
1112
|
+
}, [visible, fullscreen, fullscreenOnOpen])
|
|
1113
|
+
|
|
1114
|
+
// 工作区跟随:对齐会话 cwd(未运行则启动;运行中 host 只改"当前 workbench 目录",**不重启进程**),
|
|
1115
|
+
// 成功后 url 变化(reloadTick / folder 参数)让常驻面重新导航到新工作区。
|
|
1116
|
+
// 触发时机是"本标签挂载期间 cwd 变化",与侧栏是否可见无关(收起侧栏时 body 并不卸载)。
|
|
1117
|
+
React.useEffect(function () {
|
|
1118
|
+
if (typeof cwd !== 'string' || cwd === '') return
|
|
1119
|
+
if (lastCwdRef.current === cwd) return
|
|
1120
|
+
var changed = lastCwdRef.current !== undefined
|
|
1121
|
+
lastCwdRef.current = cwd
|
|
1122
|
+
var cancelled = false
|
|
1123
|
+
api('/code-server/start', { cwd: cwd }).then(function (s) {
|
|
1124
|
+
if (cancelled) return
|
|
1125
|
+
setState({ status: s })
|
|
1126
|
+
if (changed && s != null && s.ok === true && s.running === true) setTick(function (t) { return t + 1 })
|
|
1127
|
+
}).catch(function () { /* 由状态轮询兜底 */ })
|
|
1128
|
+
return function () { cancelled = true }
|
|
1129
|
+
}, [cwd])
|
|
1130
|
+
|
|
1131
|
+
// 解析不出工作区时**必须留痕**:这正是 0.3.46 在 DSH 0.1.6-alpha.2 上踩的坑 ——
|
|
1132
|
+
// 客户端悄悄不发 cwd,IDE 以空工作区打开,界面与日志里都没有任何线索。每次挂载只报一次。
|
|
1133
|
+
React.useEffect(function () {
|
|
1134
|
+
if (typeof cwd === 'string' && cwd !== '') return
|
|
1135
|
+
if (cwdWarnedRef.current) return
|
|
1136
|
+
cwdWarnedRef.current = true
|
|
1137
|
+
console.warn('[code-server] 未能解析当前工作区目录(会话无 cwd / 标准 prop 无 sessionId /'
|
|
1138
|
+
+ ' 工作区表也没匹配上):本次不向宿主发送 cwd,IDE 会以空工作区打开')
|
|
1139
|
+
}, [cwd])
|
|
1140
|
+
|
|
1141
|
+
// 标签挂载期间保持状态新鲜(切走即停;回来时先 GET 一次再挂 iframe)
|
|
1142
|
+
React.useEffect(function () {
|
|
1143
|
+
api('/code-server/status').then(function (s) { if (s != null) setState({ status: s }) }).catch(function () {})
|
|
1144
|
+
var timer = window.setInterval(function () {
|
|
1145
|
+
api('/code-server/status').then(function (s) { setState({ status: s }) }).catch(function () {})
|
|
1146
|
+
}, 3000)
|
|
1147
|
+
return function () { window.clearInterval(timer) }
|
|
1148
|
+
}, [])
|
|
1149
|
+
|
|
1150
|
+
// 文件定位:地址变化(或官方 openFile 带来的 line 参数)→ 让内建扩展在 workbench 里打开它。
|
|
1151
|
+
// 扩展每 800ms 轮询信号文件且失败保留重试,故实例尚未就绪时也可先写入。
|
|
1152
|
+
React.useEffect(function () {
|
|
1153
|
+
if (targetFile === null) return
|
|
1154
|
+
api('/code-server/open-file', line === null ? { file: targetFile } : { file: targetFile, line: line })
|
|
1155
|
+
.catch(function () { /* 忽略:由用户重试 */ })
|
|
1156
|
+
}, [revision, targetFile, line])
|
|
1157
|
+
|
|
1158
|
+
return React.createElement('div', { className: 'dshcs-tabroot', 'data-code-server-tab': 'body', ref: rootRef },
|
|
1159
|
+
React.createElement(CodeServerSurface, {
|
|
1160
|
+
status: status, pageUrl: buildPageUrl(status, cwd), reloadTick: tick, owner: 'tab:' + tab.id,
|
|
1161
|
+
})
|
|
1162
|
+
)
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/** 取服务:先按 ctx 属性(注入上下文的常规形态),再回退 `ctx.get(name)`。
|
|
1166
|
+
* 实测踩坑:某些上下文(desktop)只保证 `get()` 可见,属性访问可能是 undefined ——
|
|
1167
|
+
* 只读属性会让注册被静默跳过(界面表现为"卡片正常但侧栏没有入口")。 */
|
|
1168
|
+
function serviceOf(sctx, name) {
|
|
1169
|
+
if (sctx == null) return null
|
|
1170
|
+
var direct = sctx[name]
|
|
1171
|
+
if (direct != null) return direct
|
|
1172
|
+
if (typeof sctx.get === 'function') {
|
|
1173
|
+
var viaGet = sctx.get(name)
|
|
1174
|
+
if (viaGet != null) return viaGet
|
|
1175
|
+
}
|
|
1176
|
+
return null
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/** 注册右侧栏 tab 类型 + body,并接上入口桥接。返回是否注册成功(失败会显式报错,不静默)。 */
|
|
1180
|
+
function registerSidebarTab(sctx) {
|
|
1181
|
+
var tabs = serviceOf(sctx, 'sidebarRightTabs')
|
|
1182
|
+
var controller = serviceOf(sctx, 'sidebarRight')
|
|
1183
|
+
if (tabs == null || controller == null) {
|
|
1184
|
+
console.error('[code-server] 右侧栏服务上下文不可见(sidebarRightTabs='
|
|
1185
|
+
+ (tabs != null) + ', sidebarRight=' + (controller != null) + '):跳过侧栏注册')
|
|
1186
|
+
return false
|
|
1187
|
+
}
|
|
1188
|
+
var slots = serviceOf(sctx, 'slots')
|
|
1189
|
+
sctx.effect(function () {
|
|
1190
|
+
return tabs.register({
|
|
1191
|
+
id: CS_TAB_ID,
|
|
1192
|
+
kind: CS_KIND,
|
|
1193
|
+
// 产品外插件 = extension 段(rank 3),高于官方的 fallback 段(rank 1):
|
|
1194
|
+
// 官方的纯文本预览(`ui-sidebar-documentpreview`,kind text)故意用 fallback,
|
|
1195
|
+
// 它的注释写明"这是 VS Code 的文本编辑器在编辑器中的位次,任何更具体的类型都应当击败它"。
|
|
1196
|
+
priority: 'extension',
|
|
1197
|
+
// **认领文件地址**:官方 openFile(产物 chip、"交付"卡片预览、正文内联提及)以及任何
|
|
1198
|
+
// 第三方 openResource(fileAddress) 都会落到本 tab。含 `:` 的 pattern 按整址 glob 匹配。
|
|
1199
|
+
patterns: ['dsh-resource://file/**'],
|
|
1200
|
+
canOpen: function (a) { return claimsAddress(parseFileAddress(a), claimPolicy()) },
|
|
1201
|
+
// 文件 tab 用文件名当 chip 文本;页面 tab(openTab/openResource 指定 kind)仍是产品名
|
|
1202
|
+
title: function (a) {
|
|
1203
|
+
if (isPageAddress(a) || a === undefined || a === null || a === '') return 'Code Server'
|
|
1204
|
+
var name = basenameOfAddress(a)
|
|
1205
|
+
return name === '' ? 'Code Server' : name
|
|
1206
|
+
},
|
|
1207
|
+
// guide 页入口框:点它即以本类型打开一个页面 tab(替换 guide 自身)
|
|
1208
|
+
guide: [{
|
|
1209
|
+
order: 20,
|
|
1210
|
+
title: function () { return 'Code Server' },
|
|
1211
|
+
description: function () { return '在右侧栏标签里运行 VS Code 网页版,跟随当前会话工作区;产物/交付文件点击后在此打开。' },
|
|
1212
|
+
icon: CodeServerIcon,
|
|
1213
|
+
}],
|
|
1214
|
+
})
|
|
1215
|
+
}, 'code-server: sidebar tab type')
|
|
1216
|
+
if (slots != null) {
|
|
1217
|
+
sctx.effect(function () {
|
|
1218
|
+
return slots.inject('sidebar.right.pane.tab', function () {
|
|
1219
|
+
return slots.register({ name: 'sidebar.right.pane.tab', key: CS_TAB_ID }, CodeServerBody)
|
|
1220
|
+
})
|
|
1221
|
+
}, 'code-server: sidebar tab body')
|
|
1222
|
+
} else {
|
|
1223
|
+
console.error('[code-server] slots 服务不可见:侧栏标签 body 未注册')
|
|
1224
|
+
return false
|
|
1225
|
+
}
|
|
1226
|
+
// 卸载 / HMR 重载:复位载体状态(legacy 判定由下一次 apply 重新做)
|
|
1227
|
+
sctx.effect(function () {
|
|
1228
|
+
return function () {
|
|
1229
|
+
setState({ sidebarUi: 'unknown' })
|
|
1230
|
+
}
|
|
1231
|
+
}, 'code-server: sidebar mode reset')
|
|
1232
|
+
console.log('[code-server] right-sidebar tab registered (kind=' + CS_KIND + ')')
|
|
1233
|
+
return true
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// ---------- 设置(参照 auto-open-web 的自绘卡片模式) ----------
|
|
1237
|
+
// 数据通道:settingsScope(官方 settings 域,命名空间 code-server)—— 两个座位共用。
|
|
1238
|
+
// 座位(0.3.50 起两条腿都注册,谁被声明谁生效):
|
|
1239
|
+
// · `plugins.bundle.config`(DSH ≥ 0.1.6-alpha.2):插件页按**包名**派发的配置区,页面自带
|
|
1240
|
+
// 标题/面包屑,我们只出表单 + 保存控件(seat='bundle-config');
|
|
1241
|
+
// · `settings.plugin.item`(DSH ≤ 0.1.6-alpha.1):设置页里自绘的可折叠卡片(已在新版退役)。
|
|
1242
|
+
var CARD_CSS =
|
|
1243
|
+
'.dshcs-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}' +
|
|
1244
|
+
'.dshcs-card:hover{border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}' +
|
|
1245
|
+
'.dshcs-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}' +
|
|
1246
|
+
'.dshcs-cardHeader{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}' +
|
|
1247
|
+
'.dshcs-cardHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}' +
|
|
1248
|
+
'.dshcs-cardHeadText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}' +
|
|
1249
|
+
'.dshcs-cardName{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}' +
|
|
1250
|
+
'.dshcs-cardDescription{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:13px;line-height:1.5}' +
|
|
1251
|
+
'.dshcs-cardChevron{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));flex:none;transition:transform .16s;display:inline-flex}' +
|
|
1252
|
+
'.dshcs-cardChevronOpen{transform:rotate(180deg)}' +
|
|
1253
|
+
'.dshcs-cardBody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}' +
|
|
1254
|
+
'.dshcs-cardReadOnly{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:12px 0 0;font-size:12px;line-height:1.5}' +
|
|
1255
|
+
'.dshcs-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}' +
|
|
1256
|
+
'.dshcs-cardFooter{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}' +
|
|
1257
|
+
'.dshcs-cardFailed{min-width:0;color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));flex:1;margin:0;font-size:12px;line-height:1.5}' +
|
|
1258
|
+
'.dshcs-cbtn{appearance:none;font:inherit;cursor:pointer;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;border:1px solid transparent;transition:color .12s ease,background .12s ease}' +
|
|
1259
|
+
'.dshcs-cbtn:disabled{opacity:.4;cursor:default}' +
|
|
1260
|
+
'.dshcs-cbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}' +
|
|
1261
|
+
'.dshcs-cbtnOutline{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}' +
|
|
1262
|
+
'.dshcs-cbtnOutline:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,transparent)}' +
|
|
1263
|
+
'.dshcs-cbtnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-primary);font-weight:600}' +
|
|
1264
|
+
'.dshcs-cbtnPrimary:hover:not(:disabled){filter:brightness(.95)}' +
|
|
1265
|
+
'.dshcs-cbtnPrimary:active:not(:disabled){filter:brightness(.9)}' +
|
|
1266
|
+
'.dshcs-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}' +
|
|
1267
|
+
'.dshcs-field+.dshcs-field{border-top:1px solid var(--dsw-alias-border-l2)}' +
|
|
1268
|
+
'.dshcs-fieldHead{align-items:center;gap:8px;display:flex}' +
|
|
1269
|
+
'.dshcs-fieldLabel{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}' +
|
|
1270
|
+
'.dshcs-card .dshcs-hint{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;line-height:1.5}' +
|
|
1271
|
+
'.dshcs-check{display:flex;align-items:center;gap:8px;cursor:pointer}' +
|
|
1272
|
+
'.dshcs-check input{accent-color:var(--dsw-alias-brand-primary);width:15px;height:15px;margin:0;flex:none}' +
|
|
1273
|
+
'.dshcs-check input:disabled{cursor:default}' +
|
|
1274
|
+
'.dshcs-text{display:flex}' +
|
|
1275
|
+
'.dshcs-text input,.dshcs-text textarea{font:12px/1.5 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3,transparent);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 8px;width:100%;min-width:0}' +
|
|
1276
|
+
// 多行输入(0.3.52):等宽 + 软换行 + 只允许纵向拉伸;`overflow-wrap` 保证没有空格的 `!ext;!ext;…` 也能断行
|
|
1277
|
+
'.dshcs-text textarea{display:block;box-sizing:border-box;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}' +
|
|
1278
|
+
'.dshcs-text input:disabled,.dshcs-text textarea:disabled{opacity:.5;cursor:default}' +
|
|
1279
|
+
'.dshcs-text input:focus-visible,.dshcs-text textarea:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}' +
|
|
1280
|
+
// 提示里的"默认值"代码块(0.3.52):保留换行、随便哪里都能断,不再把卡片撑破
|
|
1281
|
+
'.dshcs-code{display:block;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font:11.5px/1.55 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2,transparent);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 8px;margin:4px 0 0;max-width:100%}' +
|
|
1282
|
+
'.dshcs-badges{align-items:center;gap:8px;display:inline-flex}' +
|
|
1283
|
+
'.dshcs-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}' +
|
|
1284
|
+
'.dshcs-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}' +
|
|
1285
|
+
'.dshcs-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}' +
|
|
1286
|
+
'.dshcs-reset:disabled{cursor:default}' +
|
|
1287
|
+
// 插件页配置区(0.3.50;页面自带标题/面包屑,这里只排表单与保存控件)
|
|
1288
|
+
'.dshcs-cfgpage{display:flex;flex-direction:column;gap:0}' +
|
|
1289
|
+
'.dshcs-cfgfoot{justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}'
|
|
1290
|
+
var CARD_TAG = 'dsh-code-server/Card.module.css'
|
|
1291
|
+
function injectCss(tagId, css) {
|
|
1292
|
+
if (typeof document === 'undefined') return
|
|
1293
|
+
if (document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') !== null) return
|
|
1294
|
+
var tag = document.createElement('style')
|
|
1295
|
+
tag.dataset.plugin = 'dsh-code-server'
|
|
1296
|
+
tag.dataset.pluginCss = tagId
|
|
1297
|
+
tag.textContent = css
|
|
1298
|
+
document.head.appendChild(tag)
|
|
1299
|
+
}
|
|
1300
|
+
injectCss(CARD_TAG, CARD_CSS)
|
|
1301
|
+
|
|
1302
|
+
function csIconChevron(props) {
|
|
1303
|
+
return React.createElement('svg', {
|
|
1304
|
+
width: 14, height: 14, viewBox: '0 0 14 14', fill: 'none', 'aria-hidden': true, className: props.className,
|
|
1305
|
+
},
|
|
1306
|
+
React.createElement('path', {
|
|
1307
|
+
d: 'M3.5 5.25L7 8.75L10.5 5.25', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
1308
|
+
})
|
|
1309
|
+
)
|
|
1310
|
+
}
|
|
1311
|
+
function csBtn(props) {
|
|
1312
|
+
return React.createElement('button', {
|
|
1313
|
+
type: 'button',
|
|
1314
|
+
className: 'dshcs-cbtn ' + (props.variant === 'primary' ? 'dshcs-cbtnPrimary' : 'dshcs-cbtnOutline'),
|
|
1315
|
+
disabled: props.disabled === true,
|
|
1316
|
+
onClick: props.onClick,
|
|
1317
|
+
}, props.children)
|
|
1318
|
+
}
|
|
1319
|
+
function csCheck(props) {
|
|
1320
|
+
return React.createElement('label', { className: 'dshcs-check' },
|
|
1321
|
+
React.createElement('input', {
|
|
1322
|
+
type: 'checkbox', checked: props.checked === true, disabled: props.disabled === true,
|
|
1323
|
+
onChange: function (event) { props.onChange(event.target.checked) },
|
|
1324
|
+
}),
|
|
1325
|
+
React.createElement('span', { className: 'dshcs-hint' }, props.children)
|
|
1326
|
+
)
|
|
1327
|
+
}
|
|
1328
|
+
/** 多行文本输入(0.3.52 起,用于"认领类型"这类清单)。
|
|
1329
|
+
* 为什么不是单行 input:0.3.51 之后默认值有 98 条排除项(≈700 字符),单行框只能看到开头,
|
|
1330
|
+
* 剩下全靠横向滚动 —— 既看不全也改不动。这里改用 textarea,并**按内容自动长高**(上限 12 行),
|
|
1331
|
+
* 保证默认值打开就能一眼看全;`resize: vertical` 允许手动再拉。 */
|
|
1332
|
+
function csTextarea(props) {
|
|
1333
|
+
var text = typeof props.value === 'string' ? props.value : ''
|
|
1334
|
+
var rows = Math.min(12, Math.max(3, Math.ceil(text.length / 64)))
|
|
1335
|
+
return React.createElement('label', { className: 'dshcs-text' },
|
|
1336
|
+
React.createElement('textarea', {
|
|
1337
|
+
value: props.value, disabled: props.disabled === true, rows: rows,
|
|
1338
|
+
spellCheck: false, autoComplete: 'off', wrap: 'soft', placeholder: props.placeholder,
|
|
1339
|
+
onChange: function (event) { props.onChange(event.target.value) },
|
|
1340
|
+
})
|
|
1341
|
+
)
|
|
1342
|
+
}
|
|
1343
|
+
function csBadges(props) {
|
|
1344
|
+
var children = []
|
|
1345
|
+
if (props.overridden === true) {
|
|
1346
|
+
children.push(React.createElement('span', { className: 'dshcs-badge' }, props.overriddenLabel))
|
|
1347
|
+
children.push(React.createElement('button', { type: 'button', className: 'dshcs-reset', disabled: props.disabled === true, onClick: props.onReset }, props.resetLabel))
|
|
1348
|
+
}
|
|
1349
|
+
if (children.length === 0) return null
|
|
1350
|
+
return React.createElement('span', { className: 'dshcs-badges' }, ...children)
|
|
1351
|
+
}
|
|
1352
|
+
function csCard(props) {
|
|
1353
|
+
var [open, setOpen] = React.useState(props.defaultOpen === true)
|
|
1354
|
+
var state = props.state
|
|
1355
|
+
if (state === null || state === undefined || state.available !== true) return null
|
|
1356
|
+
var title = props.title
|
|
1357
|
+
var noticeOnly = props.noticeOnly === true
|
|
1358
|
+
var saveDisabled = state.dirty !== true || state.invalid === true || state.saving === true
|
|
1359
|
+
return React.createElement('li', { className: 'dshcs-card' + (open ? ' dshcs-cardOpen' : '') },
|
|
1360
|
+
React.createElement('button', {
|
|
1361
|
+
type: 'button', className: 'dshcs-cardHeader', 'aria-expanded': open,
|
|
1362
|
+
onClick: function () { setOpen(!open) },
|
|
1363
|
+
},
|
|
1364
|
+
React.createElement('span', { className: 'dshcs-cardHeadText' },
|
|
1365
|
+
React.createElement('span', { className: 'dshcs-cardName' }, title),
|
|
1366
|
+
React.createElement('span', { className: 'dshcs-cardDescription' }, props.description)
|
|
1367
|
+
),
|
|
1368
|
+
state.dirty === true ? React.createElement('span', { className: 'dshcs-pending' }, props.unsavedLabel) : null,
|
|
1369
|
+
React.createElement(csIconChevron, { className: 'dshcs-cardChevron' + (open ? ' dshcs-cardChevronOpen' : '') })
|
|
1370
|
+
),
|
|
1371
|
+
open === true ? React.createElement('div', { className: 'dshcs-cardBody' },
|
|
1372
|
+
// 旧版 DSH 提示卡:没有可保存的项,不渲染只读条与底部按钮
|
|
1373
|
+
noticeOnly === true ? null
|
|
1374
|
+
: (state.writable !== true ? React.createElement('p', { className: 'dshcs-cardReadOnly', role: 'status' }, props.readOnlyLabel) : null),
|
|
1375
|
+
props.children,
|
|
1376
|
+
noticeOnly === true ? null : React.createElement('div', { className: 'dshcs-cardFooter' },
|
|
1377
|
+
state.failed === true ? React.createElement('p', { className: 'dshcs-cardFailed', role: 'status' }, props.saveFailedLabel) : null,
|
|
1378
|
+
React.createElement(csBtn, { disabled: state.dirty !== true || state.saving === true, onClick: props.onDiscard }, props.discardLabel),
|
|
1379
|
+
React.createElement(csBtn, { variant: 'primary', disabled: saveDisabled, onClick: props.onSave }, state.saving === true ? props.savingLabel : props.saveLabel)
|
|
1380
|
+
)
|
|
1381
|
+
) : null
|
|
1382
|
+
)
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/** 设置卡片页面:useState+useEffect 订阅 settingsScope(不投机 useSyncExternalStore
|
|
1386
|
+
* 对 snapshot 引用稳定性的要求;不建中间 store)。 */
|
|
1387
|
+
function csSettingsPage(props) {
|
|
1388
|
+
try {
|
|
1389
|
+
var scope = props.scope
|
|
1390
|
+
var [snapshot, setSnapshot] = React.useState(function () { return scope !== undefined ? scope.getSnapshot() : null })
|
|
1391
|
+
var [draft, setDraft] = React.useState(null) // null | { keepResident, fileOpenScope }(未保存草稿)
|
|
1392
|
+
var [saving, setSaving] = React.useState(false)
|
|
1393
|
+
var [failed, setFailed] = React.useState(false)
|
|
1394
|
+
// 共享 store:读取当前 UI 载体状态(右侧栏是否就绪 / 是否旧版 DSH),必须放在所有提前 return 之前
|
|
1395
|
+
var liveStore = useStore()
|
|
1396
|
+
// 常驻面实时状态(停靠/停放/降级):卡片里显示,便于判断"切标签是否还会重载"
|
|
1397
|
+
var [surfaceState, setSurfaceState] = React.useState(surfaceSnapshot())
|
|
1398
|
+
React.useEffect(function () {
|
|
1399
|
+
return subscribeSurface(function (snap) { setSurfaceState(snap) })
|
|
1400
|
+
}, [])
|
|
1401
|
+
React.useEffect(function () {
|
|
1402
|
+
if (scope === undefined || typeof scope.subscribe !== 'function') return
|
|
1403
|
+
function onUpdate() {
|
|
1404
|
+
try { setSnapshot(scope.getSnapshot()) } catch (e) { /* ignore */ }
|
|
1405
|
+
}
|
|
1406
|
+
var dispose = scope.subscribe(onUpdate)
|
|
1407
|
+
return function () { if (typeof dispose === 'function') dispose() }
|
|
1408
|
+
}, [scope])
|
|
1409
|
+
if (scope === undefined) {
|
|
1410
|
+
console.error('[code-server] card scope missing')
|
|
1411
|
+
return null
|
|
1412
|
+
}
|
|
1413
|
+
if (snapshot === null || snapshot === undefined || snapshot.status !== 'ready') {
|
|
1414
|
+
// 尚未就绪(连接/镜像加载中):静默留空,官方插槽边界接管;不显示占位
|
|
1415
|
+
if (snapshot !== null && snapshot !== undefined && snapshot.status === 'unavailable') {
|
|
1416
|
+
console.warn('[code-server] settings scope unavailable:', snapshot.status)
|
|
1417
|
+
}
|
|
1418
|
+
return null
|
|
1419
|
+
}
|
|
1420
|
+
var value = snapshot.value !== undefined && snapshot.value !== null ? snapshot.value : {}
|
|
1421
|
+
var user = snapshot.user
|
|
1422
|
+
var loaded = {
|
|
1423
|
+
keepResident: value.keepResident !== false,
|
|
1424
|
+
claimExtensions: typeof value.claimExtensions === 'string' ? value.claimExtensions : DEFAULT_CLAIM_EXTENSIONS,
|
|
1425
|
+
fullscreenOnOpen: value.fullscreenOnOpen !== false,
|
|
1426
|
+
}
|
|
1427
|
+
// ---- 旧版 DSH(无右侧栏服务):本页是唯一的提示出口,不显示任何设置项 ----
|
|
1428
|
+
if (liveStore != null && liveStore.sidebarUi === 'legacy') {
|
|
1429
|
+
return React.createElement(csCard, {
|
|
1430
|
+
title: 'Code Server',
|
|
1431
|
+
description: '当前 DSH 版本不受支持(缺少右侧栏服务)',
|
|
1432
|
+
defaultOpen: true,
|
|
1433
|
+
noticeOnly: true,
|
|
1434
|
+
state: { available: true, writable: false, dirty: false, invalid: false, saving: false, failed: false },
|
|
1435
|
+
},
|
|
1436
|
+
React.createElement('div', { className: 'dshcs-field' },
|
|
1437
|
+
React.createElement('div', { className: 'dshcs-legacy' }, '本插件自 0.2.3 起不再兼容旧版 DSH。'),
|
|
1438
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 0 } },
|
|
1439
|
+
'未检测到右侧栏插件服务 sidebarRightTabs / sidebarRight,因此插件不提供任何入口'
|
|
1440
|
+
+ '(旧版的悬浮球与浮动窗口已移除),也不会后台启动 IDE。'
|
|
1441
|
+
+ '\n升级 DSH 到带右侧栏的版本(≥ 0.1.5-alpha.1)后,Code Server 会出现在右侧栏标签里,'
|
|
1442
|
+
+ '本页同时显示完整设置项;升级后无需重装本插件,刷新页面即可。'
|
|
1443
|
+
)
|
|
1444
|
+
)
|
|
1445
|
+
)
|
|
1446
|
+
}
|
|
1447
|
+
var overriddenKeep = user !== undefined && user !== null && Object.prototype.hasOwnProperty.call(user, 'keepResident')
|
|
1448
|
+
var overriddenClaim = user !== undefined && user !== null && Object.prototype.hasOwnProperty.call(user, 'claimExtensions')
|
|
1449
|
+
var overriddenFullscreen = user !== undefined && user !== null && Object.prototype.hasOwnProperty.call(user, 'fullscreenOnOpen')
|
|
1450
|
+
var dirty = draft !== null && (draft.keepResident !== loaded.keepResident
|
|
1451
|
+
|| normalizeClaimExtensions(draft.claimExtensions) !== normalizeClaimExtensions(loaded.claimExtensions)
|
|
1452
|
+
|| draft.fullscreenOnOpen !== loaded.fullscreenOnOpen)
|
|
1453
|
+
var saveDisabled = !dirty || saving
|
|
1454
|
+
var state = {
|
|
1455
|
+
available: true,
|
|
1456
|
+
writable: snapshot.writable,
|
|
1457
|
+
dirty: dirty,
|
|
1458
|
+
invalid: false,
|
|
1459
|
+
saving: saving,
|
|
1460
|
+
failed: failed,
|
|
1461
|
+
}
|
|
1462
|
+
// 保存/恢复成功后,把 host 最新 status 推给模块共享 store → 常驻预热等立即按新设置生效。
|
|
1463
|
+
function syncStatusToStore() {
|
|
1464
|
+
api('/code-server/status').then(function (s) {
|
|
1465
|
+
if (s != null && typeof s === 'object') setState({ status: s })
|
|
1466
|
+
}).catch(function () { /* 失败由下次轮询兜底 */ })
|
|
1467
|
+
}
|
|
1468
|
+
async function doSave() {
|
|
1469
|
+
if (dirty && !saving) {
|
|
1470
|
+
setSaving(true); setFailed(false)
|
|
1471
|
+
try {
|
|
1472
|
+
var d = draft !== null ? draft : loaded
|
|
1473
|
+
await props.scope.set('keepResident', d.keepResident === true)
|
|
1474
|
+
await props.scope.set('claimExtensions', normalizeClaimExtensions(d.claimExtensions))
|
|
1475
|
+
await props.scope.set('fullscreenOnOpen', d.fullscreenOnOpen === true)
|
|
1476
|
+
setDraft(null)
|
|
1477
|
+
syncStatusToStore()
|
|
1478
|
+
} catch (e) {
|
|
1479
|
+
setFailed(true)
|
|
1480
|
+
}
|
|
1481
|
+
setSaving(false)
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
async function doReset() {
|
|
1485
|
+
if (saving || snapshot.writable !== true) return
|
|
1486
|
+
setSaving(true); setFailed(false)
|
|
1487
|
+
try {
|
|
1488
|
+
// unset 清除 user 覆盖层 → 回到 base 默认值;成功后镜像同步
|
|
1489
|
+
if (typeof props.scope.unset === 'function') {
|
|
1490
|
+
await props.scope.unset('keepResident')
|
|
1491
|
+
await props.scope.unset('claimExtensions')
|
|
1492
|
+
await props.scope.unset('fullscreenOnOpen')
|
|
1493
|
+
} else {
|
|
1494
|
+
await props.scope.set('keepResident', value.keepResident === undefined || value.keepResident === null ? true : value.keepResident)
|
|
1495
|
+
await props.scope.set('claimExtensions', typeof value.claimExtensions === 'string' ? normalizeClaimExtensions(value.claimExtensions) : DEFAULT_CLAIM_EXTENSIONS)
|
|
1496
|
+
await props.scope.set('fullscreenOnOpen', value.fullscreenOnOpen === undefined || value.fullscreenOnOpen === null ? true : value.fullscreenOnOpen)
|
|
1497
|
+
}
|
|
1498
|
+
setDraft(null)
|
|
1499
|
+
syncStatusToStore()
|
|
1500
|
+
} catch (e) {
|
|
1501
|
+
setFailed(true)
|
|
1502
|
+
}
|
|
1503
|
+
setSaving(false)
|
|
1504
|
+
}
|
|
1505
|
+
var fields = [
|
|
1506
|
+
// 三个设置(0.2.11 起):认领类型 + 打开即全屏 + 后台常驻。入口/依赖安装/环境检测三行已移除
|
|
1507
|
+
// (入口在右侧栏「开始」页的 guide 入口框;诊断看 host 日志里的 [code-server] 输出)。
|
|
1508
|
+
React.createElement('div', { className: 'dshcs-field' },
|
|
1509
|
+
React.createElement('div', { className: 'dshcs-fieldHead' },
|
|
1510
|
+
React.createElement('span', { className: 'dshcs-fieldLabel' }, '认领类型(按扩展名,分号分隔)'),
|
|
1511
|
+
overriddenClaim === true
|
|
1512
|
+
? React.createElement(csBadges, {
|
|
1513
|
+
overridden: true, disabled: snapshot.writable !== true,
|
|
1514
|
+
overriddenLabel: '已覆盖', resetLabel: '恢复默认',
|
|
1515
|
+
onReset: function () { doReset() },
|
|
1516
|
+
})
|
|
1517
|
+
: null
|
|
1518
|
+
),
|
|
1519
|
+
React.createElement(csTextarea, {
|
|
1520
|
+
value: draft !== null ? draft.claimExtensions : loaded.claimExtensions,
|
|
1521
|
+
disabled: snapshot.writable !== true,
|
|
1522
|
+
placeholder: '例如 py;ts;!md(留空 = 不认领任何文件)',
|
|
1523
|
+
onChange: function (v) { setDraft(function (prev) { return Object.assign({}, prev !== null ? prev : loaded, { claimExtensions: v }) }); setFailed(false) },
|
|
1524
|
+
}),
|
|
1525
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1526
|
+
'写扩展名(不带点,大小写随意);`*` = 其余类型也认领;`!ext` = 不认领(排除优先)。'
|
|
1527
|
+
+ '不再区分会话内/绝对路径 —— 所有 `dsh-resource://file/**` 一视同仁,未认领的落回 DSH 自带预览。'),
|
|
1528
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1529
|
+
'实际规则:' + describeClaimPolicy(draft !== null ? draft.claimExtensions : loaded.claimExtensions)
|
|
1530
|
+
+ (normalizeClaimExtensions(draft !== null ? draft.claimExtensions : loaded.claimExtensions)
|
|
1531
|
+
!== (draft !== null ? draft.claimExtensions : loaded.claimExtensions) ? '(保存后归一化)' : '')),
|
|
1532
|
+
// 提示块(0.3.52):分三行写清楚 + 默认值**折成多行**显示。
|
|
1533
|
+
// 以前这里是一整行"… · 默认 <700 字符>" —— `;` 在 CSS 里不是断行点,那串既不折行也复制不全,
|
|
1534
|
+
// 直接把卡片撑破。现在:三组各自成行(与 lib/claim-types.js 的常量同源),换行与分号等价,
|
|
1535
|
+
// 所以整段连换行一起复制回输入框,解析结果与官方默认值逐字相同。
|
|
1536
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 6 } },
|
|
1537
|
+
'默认排除三类:① DSH 预览渲染得好的 markdown/html/图片/PDF;'
|
|
1538
|
+
+ '② 可执行文件与二进制产物(exe/dll/msi/jar/so…);'
|
|
1539
|
+
+ '③ Office 与版式文档(docx/xlsx/pptx/vsdx…)。'
|
|
1540
|
+
+ '文本脚本(bat/cmd/ps1/sh/py)与 csv/tsv 仍进 IDE。'
|
|
1541
|
+
+ '想整组放开,把下面这段换成 `*;!md;!html;!png;!jpg;!pdf` 之类的短清单即可。'),
|
|
1542
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1543
|
+
'默认值(可整段复制;换行与分号等价):',
|
|
1544
|
+
React.createElement('code', { className: 'dshcs-code', 'data-claim-default': 'lines' }, claimDefaultLines())
|
|
1545
|
+
)
|
|
1546
|
+
),
|
|
1547
|
+
React.createElement('div', { className: 'dshcs-field' },
|
|
1548
|
+
React.createElement('div', { className: 'dshcs-fieldHead' },
|
|
1549
|
+
React.createElement('span', { className: 'dshcs-fieldLabel' }, '打开即全屏(右侧栏铺满窗口)'),
|
|
1550
|
+
overriddenFullscreen === true
|
|
1551
|
+
? React.createElement(csBadges, {
|
|
1552
|
+
overridden: true, disabled: snapshot.writable !== true,
|
|
1553
|
+
overriddenLabel: '已覆盖', resetLabel: '恢复默认',
|
|
1554
|
+
onReset: function () { doReset() },
|
|
1555
|
+
})
|
|
1556
|
+
: null
|
|
1557
|
+
),
|
|
1558
|
+
React.createElement(csCheck, {
|
|
1559
|
+
checked: draft !== null ? draft.fullscreenOnOpen : loaded.fullscreenOnOpen,
|
|
1560
|
+
disabled: snapshot.writable !== true,
|
|
1561
|
+
onChange: function (v) { setDraft(function (prev) { return Object.assign({}, prev !== null ? prev : loaded, { fullscreenOnOpen: v === true }) }); setFailed(false) },
|
|
1562
|
+
}, '打开 Code Server 标签(含点开产物/交付文件)时,自动把右侧栏从"与对话并排"切到全屏;'
|
|
1563
|
+
+ '想同时看对话就关掉它,或随时点右侧栏的「退出全屏」—— 本次打开不会被抢回去'),
|
|
1564
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1565
|
+
'只影响"打开那一刻":切走再切回、再次打开文件 tab 会重新切全屏;'
|
|
1566
|
+
+ 'DSH 未向插件开放模式接口,本项由点击右侧栏自身的「全屏」按钮实现。')
|
|
1567
|
+
),
|
|
1568
|
+
React.createElement('div', { className: 'dshcs-field' },
|
|
1569
|
+
React.createElement('div', { className: 'dshcs-fieldHead' },
|
|
1570
|
+
React.createElement('span', { className: 'dshcs-fieldLabel' }, '后台常驻(切标签不重载)'),
|
|
1571
|
+
overriddenKeep === true
|
|
1572
|
+
? React.createElement(csBadges, {
|
|
1573
|
+
overridden: true, disabled: snapshot.writable !== true,
|
|
1574
|
+
overriddenLabel: '已覆盖', resetLabel: '恢复默认',
|
|
1575
|
+
onReset: function () { doReset() },
|
|
1576
|
+
})
|
|
1577
|
+
: null
|
|
1578
|
+
),
|
|
1579
|
+
React.createElement(csCheck, {
|
|
1580
|
+
checked: draft !== null ? draft.keepResident : loaded.keepResident,
|
|
1581
|
+
disabled: snapshot.writable !== true,
|
|
1582
|
+
onChange: function (v) { setDraft(function (prev) { return Object.assign({}, prev !== null ? prev : loaded, { keepResident: v === true }) }); setFailed(false) },
|
|
1583
|
+
}, '开启后宿主启动即把 IDE 加载到后台"停放区":切换右侧栏标签、收起/展开侧栏、拖成浮动窗口都不再重载,首次打开免等待;关闭则只在打开面板时加载(省内存)'),
|
|
1584
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1585
|
+
supportsMoveBefore
|
|
1586
|
+
? '常驻面:' + (surfaceState.docked ? '已停靠' : (surfaceState.ready ? '已停放(后台运行中)' : '未启动'))
|
|
1587
|
+
+ (surfaceState.degraded ? ' · 本次发生过降级重载' : '')
|
|
1588
|
+
: '当前浏览器不支持 Element.moveBefore(Chromium <133)→ 常驻不可用,切标签仍会整页重载(升级浏览器后自动生效)')
|
|
1589
|
+
)
|
|
1590
|
+
]
|
|
1591
|
+
// ---- 插件页的配置区(DSH ≥ 0.1.6-alpha.2)----
|
|
1592
|
+
// 上游把插件设置从"设置 → 插件"搬到**插件页**:页面自己画标题/图标/面包屑,把
|
|
1593
|
+
// `plugins.bundle.config`(按包名为键)渲染在描述与行之间,只问 `view: 'page'`。
|
|
1594
|
+
// 所以这里不再套可折叠卡片(标题会与页面标题重复),只出表单 + 自己的保存控件
|
|
1595
|
+
// (契约原文:the form with its own save control)。
|
|
1596
|
+
if (props.seat === 'bundle-config') {
|
|
1597
|
+
return React.createElement('div', { className: 'dshcs-cfgpage', 'data-code-server-config': 'page' },
|
|
1598
|
+
snapshot.writable !== true
|
|
1599
|
+
? React.createElement('p', { className: 'dshcs-cardReadOnly', role: 'status' }, '本部署的设置为只读。')
|
|
1600
|
+
: null,
|
|
1601
|
+
...fields,
|
|
1602
|
+
failed === true
|
|
1603
|
+
? React.createElement('p', { className: 'dshcs-cardFailed', role: 'status' }, '本部署没有接受这些值,已保留供你修改。')
|
|
1604
|
+
: null,
|
|
1605
|
+
React.createElement('div', { className: 'dshcs-cfgfoot' },
|
|
1606
|
+
React.createElement(csBtn, {
|
|
1607
|
+
disabled: !dirty || saving,
|
|
1608
|
+
onClick: function () { setDraft(null); setFailed(false) },
|
|
1609
|
+
}, '放弃修改'),
|
|
1610
|
+
React.createElement(csBtn, {
|
|
1611
|
+
variant: 'primary', disabled: saveDisabled, onClick: function () { doSave() },
|
|
1612
|
+
}, saving === true ? '保存中…' : '保存')
|
|
1613
|
+
)
|
|
1614
|
+
)
|
|
1615
|
+
}
|
|
1616
|
+
return React.createElement(csCard, {
|
|
1617
|
+
title: 'Code Server',
|
|
1618
|
+
description: '入口:右侧栏标签(DSH ≥ 0.1.5-alpha.1);认领类型决定哪些文件交给 Code Server 打开',
|
|
1619
|
+
state: state,
|
|
1620
|
+
unsavedLabel: '未保存', readOnlyLabel: '本部署的设置为只读。',
|
|
1621
|
+
saveFailedLabel: '本部署没有接受这些值,已保留供你修改。',
|
|
1622
|
+
discardLabel: '放弃修改', saveLabel: '保存', savingLabel: '保存中…',
|
|
1623
|
+
onSave: doSave, onDiscard: function () { setDraft(null); setFailed(false) },
|
|
1624
|
+
}, ...fields)
|
|
1625
|
+
} catch (e) {
|
|
1626
|
+
// 渲染异常:记日志不打断;官方插槽对异常有边界,卡片留空即可
|
|
1627
|
+
console.error('[code-server] card render error:', e !== null && e !== undefined && e.message !== undefined ? e.message : String(e))
|
|
1628
|
+
return null
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/** 插件页配置区的入口组件(0.3.50)。
|
|
1633
|
+
* 上游契约:同一个 entry 会被要两种视图 —— `summary`(标题下的一句话)与 `page`(表单,
|
|
1634
|
+
* 自带保存控件)。插件页对 `plugins.bundle.config` 只用 `page`,但两种都答得起才算合格。
|
|
1635
|
+
* 本组件自身不调 hook(两个分支都不调),真正的设置表单在 csSettingsPage 里(独立组件边界)。 */
|
|
1636
|
+
function BundleConfigEntry(props) {
|
|
1637
|
+
if (props != null && props.view === 'summary') return CS_CONFIG_SUMMARY
|
|
1638
|
+
return React.createElement(csSettingsPage, Object.assign({}, props, { seat: 'bundle-config' }))
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// ---------- 「问 DSH」对话框(0.3.24;0.3.59 起面板本体就是这份源码)----------
|
|
1642
|
+
// 对话**不在编辑器的侧栏/tab 里**,而是作为一个浮在 DSH 页面上的对话框(用户的原话:
|
|
1643
|
+
// "对话不要用侧边栏,还是改成对话框形式")。这里就是那个对话框:**外壳**(标题栏 / 拖动 / 关闭)
|
|
1644
|
+
// + **面板本体**(对话流 / 授权卡片 / 输入框)都在本文件里。
|
|
1645
|
+
// - 定时问 host `/ask/state?rev=N`,没变就只回一个数字(不重传对话流);
|
|
1646
|
+
// - 面板里的 ask / approve / close 消息 → host 的 `/ask/send|approve|close`。
|
|
1647
|
+
//
|
|
1648
|
+
// 0.3.24–0.3.58 的面板本体是**另一个构建产物**:host 读盘 webview/thread.js + thread.css 交给
|
|
1649
|
+
// `/ask/bundle`,这里把 CSS 塞进 <style>、JS 塞进 <script> 执行(还得先给它一个假的
|
|
1650
|
+
// acquireVsCodeApi)。那套机制唯一的存在理由,是"面板要跟 DSH 界面用同一个官方渲染器,
|
|
1651
|
+
// 而编辑器 webview 里没有 DSH 的模块表" —— 可对话框本来就跑在 **DSH 页面里**:
|
|
1652
|
+
// 壳的模块表(dsh-web-frontend 的 staticModules)已经冻结了
|
|
1653
|
+
// react / react/jsx-runtime / react-dom / react-dom/client / @deepseek-ai/dsh-client-ui-primitives / …
|
|
1654
|
+
// 所以 0.3.59 直接 require 它们,面板就是普通源码:
|
|
1655
|
+
// · 一次打包都不需要(改这个文件即改面板);
|
|
1656
|
+
// · 用的是**当前页面这一份**渲染器 ⇒ 不可能再有"面板内置 alpha.1、而界面是 alpha.2"
|
|
1657
|
+
// 的排版差异(那正是老 app.jsx 要专门显示一行版本告警的原因);
|
|
1658
|
+
// · 代码高亮走 DSH 自己的 shiki 懒加载语法集(产物里只能带 typescript/shellscript/json 三套);
|
|
1659
|
+
// · 公式的 KaTeX 样式与 --dsw-* 设计令牌都由页面提供(official-tokens.css 那 28KB 副本不再需要)。
|
|
1660
|
+
// 拿不到模块表(异常宿主)时**降级而不是白屏**:正文退成 <pre>、按钮退成原生 button,
|
|
1661
|
+
// 并在控制台说清原因 —— 空白面板是最难查的一种失败。
|
|
1662
|
+
var ASK_POLL_MS = 900
|
|
1663
|
+
var ask = {
|
|
1664
|
+
rev: -1,
|
|
1665
|
+
open: false,
|
|
1666
|
+
mounted: false,
|
|
1667
|
+
pending: null,
|
|
1668
|
+
el: null, body: null, title: null, timer: null,
|
|
1669
|
+
/** React 根(只在第一次打开时建一次)。 */
|
|
1670
|
+
root: null,
|
|
1671
|
+
/** 面板挂上来的"把宿主状态推进视图"函数(挂载完成前为 null)。 */
|
|
1672
|
+
push: null,
|
|
1673
|
+
/** `askPrimitives()` 的解析结果(只算一次)。 */
|
|
1674
|
+
renderer: null,
|
|
1675
|
+
/** 上一条提问的投递方式提示(宿主回话里带 delivery;用来消除"追问没反应"的错觉)。 */
|
|
1676
|
+
deliveryNote: null,
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/** 投递结果 → 状态行文案(投递方式跟 DSH 设置 `ui-conversation.busyEnter` 走,见 host 的 pickBusyEnter)。
|
|
1680
|
+
* **必须同时看 busy**:空闲时宿主也用 followup(两种方式等价),但那时消息是**立刻开新一轮**;
|
|
1681
|
+
* 只看 delivery 会说成"排到下一轮",与事实不符(实测踩到:空闲时发一条,面板却写"当前轮结束后…")。 */
|
|
1682
|
+
function askDeliveryNote(delivery, busy) {
|
|
1683
|
+
if (busy !== true) return '已发送:DSH 会立刻开始新一轮。'
|
|
1684
|
+
if (delivery === 'steer') return '已插入当前轮:它会在下一个步骤边界读到这条追问。'
|
|
1685
|
+
if (delivery === 'queue') return '已排入下一轮:当前轮结束后会作为新的一轮回答。'
|
|
1686
|
+
return '已发送,等待 DSH 处理。'
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
/** 面板 → 对话框壳的消息通道(替身 acquireVsCodeApi 把消息交到这里)。 */
|
|
1690
|
+
function askOnMessage(message) {
|
|
1691
|
+
if (message === null || typeof message !== 'object') return
|
|
1692
|
+
if (message.type === 'ready') { askPush(true); return }
|
|
1693
|
+
if (message.type === 'ask' && typeof message.text === 'string') {
|
|
1694
|
+
ask.deliveryNote = null
|
|
1695
|
+
api('/code-server/ask/send', { text: message.text, mode: ask.pending === null ? undefined : ask.pending.mode })
|
|
1696
|
+
.then(function (result) {
|
|
1697
|
+
// 宿主按 DSH 的设置决定"排队"还是"插入当前轮";把这个结果说出来 ——
|
|
1698
|
+
// 否则用户在主界面看不到消息的那段时间里会以为它丢了(实测就是这么反馈的)。
|
|
1699
|
+
if (result != null && result.ok === true) ask.deliveryNote = askDeliveryNote(result.delivery, result.busy)
|
|
1700
|
+
ask.rev = -1
|
|
1701
|
+
askPush(true)
|
|
1702
|
+
})
|
|
1703
|
+
.catch(function () { /* 下一趟轮询会带回状态 */ })
|
|
1704
|
+
return
|
|
1705
|
+
}
|
|
1706
|
+
if (message.type === 'approve' && typeof message.id === 'string') {
|
|
1707
|
+
api('/code-server/ask/approve', { id: message.id, outcome: message.outcome })
|
|
1708
|
+
.then(function (result) {
|
|
1709
|
+
if (result != null && result.ok === false) askNotice(result.error || '授权提交失败')
|
|
1710
|
+
ask.rev = -1
|
|
1711
|
+
askPush(true)
|
|
1712
|
+
})
|
|
1713
|
+
.catch(function (err) { askNotice('授权提交失败:' + String(err && err.message ? err.message : err)) })
|
|
1714
|
+
return
|
|
1715
|
+
}
|
|
1716
|
+
if (message.type === 'close') askClose()
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/** 把 host 的状态推进面板(面板挂载时把自己登记到 `ask.push`)。 */
|
|
1720
|
+
function askPush(force) {
|
|
1721
|
+
if (ask.push === null) {
|
|
1722
|
+
// 面板还没挂上(第一次打开时 React 挂载是异步的)→ 稍后补一次,别丢掉这份状态。
|
|
1723
|
+
if (force === true) setTimeout(function () { askPush(false) }, 120)
|
|
1724
|
+
return
|
|
1725
|
+
}
|
|
1726
|
+
// 投入方式是**客户端侧**知道的(宿主 /ask/send 的回话),宿主状态里没有它 ⇒ 随状态一起带上。
|
|
1727
|
+
ask.push(ask.pending === null ? null : Object.assign({}, ask.pending, { deliveryNote: ask.deliveryNote }))
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/** 状态行里说一句(面板自己在 statusText 里显示;这里只兜底日志)。 */
|
|
1731
|
+
function askNotice(text) {
|
|
1732
|
+
console.warn('[code-server] ask dialog:', text)
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function askClose() {
|
|
1736
|
+
ask.open = false
|
|
1737
|
+
if (ask.el !== null) ask.el.style.display = 'none'
|
|
1738
|
+
api('/code-server/ask/close', {}).catch(function () { /* 下一趟轮询会纠正 */ })
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
/** 造对话框外壳(只造一次):头部(标题 / 上下文 / ✕)+ 面板挂载点。 */
|
|
1742
|
+
function askEnsureShell() {
|
|
1743
|
+
if (ask.el !== null) return
|
|
1744
|
+
var el = document.createElement('div')
|
|
1745
|
+
el.className = 'dshcs-dialog'
|
|
1746
|
+
el.setAttribute('data-dshcs-dialog', 'ask')
|
|
1747
|
+
el.style.cssText = [
|
|
1748
|
+
'position:fixed', 'right:24px', 'bottom:24px', 'width:460px', 'height:600px',
|
|
1749
|
+
'min-width:320px', 'min-height:280px', 'max-width:96vw', 'max-height:92vh',
|
|
1750
|
+
'resize:both', 'overflow:hidden', 'display:none', 'z-index:2147483000',
|
|
1751
|
+
'border-radius:10px', 'box-shadow:0 18px 48px rgba(0,0,0,.45)',
|
|
1752
|
+
'border:1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.4))',
|
|
1753
|
+
'background:var(--dsw-alias-bg-base, var(--vscode-editor-background, #1e1e1e))',
|
|
1754
|
+
].join(';')
|
|
1755
|
+
var head = document.createElement('div')
|
|
1756
|
+
head.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 10px;cursor:move;'
|
|
1757
|
+
+ 'border-bottom:1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.3));'
|
|
1758
|
+
+ 'font:var(--dsw-font-markdown-base, 13px/20px sans-serif)'
|
|
1759
|
+
var title = document.createElement('span')
|
|
1760
|
+
title.textContent = 'DSH 对话'
|
|
1761
|
+
title.style.cssText = 'font-weight:600;flex:none'
|
|
1762
|
+
var where = document.createElement('span')
|
|
1763
|
+
where.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'
|
|
1764
|
+
+ 'color:var(--dsw-alias-label-tertiary, rgba(128,128,128,.9));font-size:12px'
|
|
1765
|
+
var close = document.createElement('button')
|
|
1766
|
+
close.type = 'button'
|
|
1767
|
+
close.textContent = '✕'
|
|
1768
|
+
close.title = '关闭'
|
|
1769
|
+
close.setAttribute('aria-label', '关闭')
|
|
1770
|
+
close.style.cssText = 'flex:none;border:0;background:transparent;color:inherit;cursor:pointer;'
|
|
1771
|
+
+ 'font-size:13px;line-height:1;padding:2px 6px;border-radius:4px'
|
|
1772
|
+
close.addEventListener('click', function () { askClose() })
|
|
1773
|
+
head.appendChild(title)
|
|
1774
|
+
head.appendChild(where)
|
|
1775
|
+
head.appendChild(close)
|
|
1776
|
+
var body = document.createElement('div')
|
|
1777
|
+
body.className = 'dshcs-panel'
|
|
1778
|
+
body.setAttribute('data-dshcs-ask-root', '')
|
|
1779
|
+
body.style.cssText = 'height:calc(100% - 33px);overflow:hidden'
|
|
1780
|
+
el.appendChild(head)
|
|
1781
|
+
el.appendChild(body)
|
|
1782
|
+
document.body.appendChild(el)
|
|
1783
|
+
ask.el = el
|
|
1784
|
+
ask.body = body
|
|
1785
|
+
ask.title = where
|
|
1786
|
+
// 拖动:按住头部移动(位置只在本次会话内有效,不做持久化)。
|
|
1787
|
+
var drag = null
|
|
1788
|
+
head.addEventListener('pointerdown', function (event) {
|
|
1789
|
+
if (event.target === close) return
|
|
1790
|
+
var rect = el.getBoundingClientRect()
|
|
1791
|
+
drag = { x: event.clientX, y: event.clientY, left: rect.left, top: rect.top }
|
|
1792
|
+
el.style.right = 'auto'
|
|
1793
|
+
el.style.bottom = 'auto'
|
|
1794
|
+
el.style.left = rect.left + 'px'
|
|
1795
|
+
el.style.top = rect.top + 'px'
|
|
1796
|
+
try { head.setPointerCapture(event.pointerId) } catch (e) { /* 忽略 */ }
|
|
1797
|
+
})
|
|
1798
|
+
head.addEventListener('pointermove', function (event) {
|
|
1799
|
+
if (drag === null) return
|
|
1800
|
+
el.style.left = Math.max(0, Math.min(window.innerWidth - 80, drag.left + (event.clientX - drag.x))) + 'px'
|
|
1801
|
+
el.style.top = Math.max(0, Math.min(window.innerHeight - 40, drag.top + (event.clientY - drag.y))) + 'px'
|
|
1802
|
+
})
|
|
1803
|
+
head.addEventListener('pointerup', function () { drag = null })
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// ---- 官方部件(模块表;解析失败就降级,绝不白屏)----
|
|
1807
|
+
|
|
1808
|
+
/**
|
|
1809
|
+
* 从官方 primitives 里取一个部件。
|
|
1810
|
+
*
|
|
1811
|
+
* **必须函数与 memo 对象都认**:官方 `MarkdownText` 是 `React.memo(...)` 的产物 —— 它的值是一个
|
|
1812
|
+
* `{$$typeof: Symbol(react.memo), type: …}` **对象**,不是函数。0.3.59 的第一版写成
|
|
1813
|
+
* `typeof x === 'function'` 才认,于是实机上正文**整个退成了 `<pre>`**(而 `DisclosureRow` /
|
|
1814
|
+
* `Button` / 图标都是函数,照常工作,于是看起来"面板好好的")。这条只有真页面能测出来 ——
|
|
1815
|
+
* 单测的桩当时写成了函数。现在两个形状都收,桩也改成了 memo 形状。
|
|
1816
|
+
*/
|
|
1817
|
+
function askComponent(primitives, name) {
|
|
1818
|
+
if (primitives === null || primitives === undefined) return null
|
|
1819
|
+
var value = primitives[name]
|
|
1820
|
+
if (value === null || value === undefined) return null
|
|
1821
|
+
if (typeof value === 'function') return value
|
|
1822
|
+
// React.memo / forwardRef / lazy:对象且带 $$typeof ⇒ createElement 认它。
|
|
1823
|
+
if (typeof value === 'object' && value.$$typeof !== undefined) return value
|
|
1824
|
+
return null
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
/** 面板要用的官方部件:React 根 + DSH 官方 UI primitives。
|
|
1828
|
+
*
|
|
1829
|
+
* 两个都从**壳的模块表**取(`dsh-web-frontend` 的 staticModules 冻结的那几个种子词):
|
|
1830
|
+
* · `react-dom/client` —— 我们自己的根(对话框容器是我们的 div,不能借用 DSH 的根);
|
|
1831
|
+
* · `@deepseek-ai/dsh-client-ui-primitives` —— MarkdownText / DisclosureRow / Button / 图标,
|
|
1832
|
+
* 与 DSH 界面**同一份实例**:排版、代码高亮、公式都与界面一致,而且不会版本错配。
|
|
1833
|
+
*
|
|
1834
|
+
* 取不到(异常宿主 / 未来 DSH 改了这个种子词)= 降级:正文 <pre>、按钮原生 button。
|
|
1835
|
+
* 这是刻意的:面板少一层排版也不能开不出窗。
|
|
1836
|
+
*/
|
|
1837
|
+
function askPrimitives() {
|
|
1838
|
+
if (ask.renderer === null) {
|
|
1839
|
+
var out = { createRoot: null, primitives: null, why: '' }
|
|
1840
|
+
try {
|
|
1841
|
+
out.createRoot = require('react-dom/client').createRoot
|
|
1842
|
+
} catch (e) {
|
|
1843
|
+
out.why = 'react-dom/client(' + (e !== null && e !== undefined && e.message !== undefined ? e.message : String(e)) + ')'
|
|
1844
|
+
}
|
|
1845
|
+
try {
|
|
1846
|
+
out.primitives = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
1847
|
+
} catch (e) {
|
|
1848
|
+
out.why += (out.why === '' ? '' : '; ') + 'dsh-client-ui-primitives(' + (e !== null && e !== undefined && e.message !== undefined ? e.message : String(e)) + ')'
|
|
1849
|
+
}
|
|
1850
|
+
if (out.createRoot == null || out.primitives == null) {
|
|
1851
|
+
console.warn('[code-server] 「问 DSH」面板降级为纯文本渲染:模块表里取不到 ' + out.why)
|
|
1852
|
+
}
|
|
1853
|
+
ask.renderer = out
|
|
1854
|
+
}
|
|
1855
|
+
return ask.renderer
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// ---- 面板视图状态(纯函数;与 host `/ask/state` 的扁平载荷一一对应)----
|
|
1859
|
+
|
|
1860
|
+
/** 授权窗口的兜底值(毫秒);host 每趟都会给真实值。 */
|
|
1861
|
+
var ASK_HOLD_MS = 8000
|
|
1862
|
+
|
|
1863
|
+
/** 面板视图的首帧状态(形状与 askApplyPayload 的输出一致)。 */
|
|
1864
|
+
function askViewState() {
|
|
1865
|
+
return {
|
|
1866
|
+
entries: [], approvals: [], approvalHoldMs: ASK_HOLD_MS,
|
|
1867
|
+
contextText: '', statusText: '', status: 'idle', error: null,
|
|
1868
|
+
sessionId: null, available: null, threadError: null, uiVersion: null,
|
|
1869
|
+
/** 客户端侧提示:上一条提问是"排队"还是"插入当前轮"(宿主状态里没有这个字段)。 */
|
|
1870
|
+
deliveryNote: null,
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/** 把宿主一趟状态并进视图(**只认白名单字段**,宿主塞别的东西也进不来)。 */
|
|
1875
|
+
function askApplyPayload(view, payload) {
|
|
1876
|
+
if (payload === null || typeof payload !== 'object' || payload.type !== 'state') return view
|
|
1877
|
+
return {
|
|
1878
|
+
entries: Array.isArray(payload.entries) ? payload.entries : [],
|
|
1879
|
+
approvals: Array.isArray(payload.approvals) ? payload.approvals : [],
|
|
1880
|
+
approvalHoldMs: Number.isSafeInteger(payload.approvalHoldMs) && payload.approvalHoldMs > 0
|
|
1881
|
+
? payload.approvalHoldMs
|
|
1882
|
+
: ASK_HOLD_MS,
|
|
1883
|
+
contextText: typeof payload.contextText === 'string' ? payload.contextText : '',
|
|
1884
|
+
statusText: typeof payload.statusText === 'string' ? payload.statusText : '',
|
|
1885
|
+
status: typeof payload.status === 'string' ? payload.status : 'idle',
|
|
1886
|
+
error: typeof payload.error === 'string' ? payload.error : null,
|
|
1887
|
+
sessionId: typeof payload.sessionId === 'string' ? payload.sessionId : null,
|
|
1888
|
+
available: payload.available === undefined ? null : payload.available,
|
|
1889
|
+
threadError: typeof payload.threadError === 'string' ? payload.threadError : null,
|
|
1890
|
+
uiVersion: typeof payload.uiVersion === 'string' ? payload.uiVersion : null,
|
|
1891
|
+
deliveryNote: typeof payload.deliveryNote === 'string' ? payload.deliveryNote : null,
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// ---- 面板样式 ----
|
|
1896
|
+
//
|
|
1897
|
+
// 原样搬 webview/src/panel.css(0.2.3 起那套外观),只做三处**必要**改动:
|
|
1898
|
+
// ① 不再 @import official-tokens.css —— 设计令牌由 DSH 页面自己提供(z 那 576 行副本是给
|
|
1899
|
+
// webview 用的,页面里本来就有,重定义一次只是幂等);
|
|
1900
|
+
// ② 去掉 `:root { color-scheme }` —— 往宿主页面注入 CSS 时**只能**碰 .dshcs-* 自己的选择器,
|
|
1901
|
+
// 改 :root/body 会动到整个 DSH 界面(这条由 test-ask-panel-inline 的选择器白名单守着);
|
|
1902
|
+
// ③ 表单控件的 --vscode-* 变量在 DSH 页面里不存在,补上页面令牌作为 fallback(否则按钮/输入框
|
|
1903
|
+
// 的 background 是"计算值无效"⇒ 透明,这是老面板在页面里看起来发灰的真实原因)。
|
|
1904
|
+
// 按钮改走官方 primitives 的 Button(与界面同一套),`.dshcs-btn` 只在降级路径下生效。
|
|
1905
|
+
var ASK_CSS = `
|
|
1906
|
+
.dshcs-panel {
|
|
1907
|
+
height: 100%;
|
|
1908
|
+
margin: 0;
|
|
1909
|
+
background: var(--vscode-editor-background, var(--dsw-alias-bg-base, #1e1e1e));
|
|
1910
|
+
color: var(--vscode-foreground, var(--dsw-alias-label-primary, #ccc));
|
|
1911
|
+
font-family: var(--vscode-font-family, var(--dsw-font-family, sans-serif));
|
|
1912
|
+
font-size: var(--vscode-font-size, 13px);
|
|
1913
|
+
overflow: hidden;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
.dshcs-app {
|
|
1917
|
+
display: flex;
|
|
1918
|
+
flex-direction: column;
|
|
1919
|
+
height: 100%;
|
|
1920
|
+
width: 100%;
|
|
1921
|
+
max-width: 900px;
|
|
1922
|
+
margin: 0 auto;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
.dshcs-header {
|
|
1926
|
+
display: flex;
|
|
1927
|
+
align-items: baseline;
|
|
1928
|
+
gap: 8px;
|
|
1929
|
+
padding: 8px 12px;
|
|
1930
|
+
border-bottom: 1px solid var(--vscode-panel-border, var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.35)));
|
|
1931
|
+
font-size: 12px;
|
|
1932
|
+
opacity: 0.9;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
.dshcs-header .dshcs-title { font-weight: 600; letter-spacing: 0.04em; }
|
|
1936
|
+
|
|
1937
|
+
.dshcs-header .dshcs-where {
|
|
1938
|
+
flex: 1;
|
|
1939
|
+
overflow: hidden;
|
|
1940
|
+
text-overflow: ellipsis;
|
|
1941
|
+
white-space: nowrap;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
.dshcs-header .dshcs-close {
|
|
1945
|
+
border: none;
|
|
1946
|
+
background: transparent;
|
|
1947
|
+
color: inherit;
|
|
1948
|
+
cursor: pointer;
|
|
1949
|
+
font-size: 13px;
|
|
1950
|
+
line-height: 1;
|
|
1951
|
+
padding: 2px 6px;
|
|
1952
|
+
border-radius: 4px;
|
|
1953
|
+
opacity: 0.75;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
.dshcs-header .dshcs-close:hover {
|
|
1957
|
+
background: var(--vscode-toolbar-hoverBackground, var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.2)));
|
|
1958
|
+
opacity: 1;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
/* ---- 思考行(默认收起;外形照抄官方 ReasoningRow 的排版语言)---- */
|
|
1962
|
+
|
|
1963
|
+
.dshcs-think { display: flex; flex-direction: column; margin: 2px 0 6px; }
|
|
1964
|
+
|
|
1965
|
+
.dshcs-think .dshcs-think-separator {
|
|
1966
|
+
flex: none;
|
|
1967
|
+
width: 2px;
|
|
1968
|
+
height: 2px;
|
|
1969
|
+
margin: 0 8px;
|
|
1970
|
+
border-radius: 1px;
|
|
1971
|
+
background: var(--dsw-alias-label-caption, currentColor);
|
|
1972
|
+
display: inline-block;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
.dshcs-think .dshcs-think-summary {
|
|
1976
|
+
min-width: 0;
|
|
1977
|
+
overflow: hidden;
|
|
1978
|
+
flex: 1 1 auto;
|
|
1979
|
+
color: var(--dsw-alias-label-tertiary, var(--vscode-descriptionForeground, rgba(128, 128, 128, 0.95)));
|
|
1980
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
1981
|
+
white-space: nowrap;
|
|
1982
|
+
text-overflow: ellipsis;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
.dshcs-think .dshcs-think-body {
|
|
1986
|
+
padding: 4px 0 4px calc(22px + var(--dsh-content-font-delta, 0px));
|
|
1987
|
+
color: var(--dsw-alias-label-tertiary, var(--vscode-descriptionForeground, rgba(128, 128, 128, 0.95)));
|
|
1988
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
1989
|
+
line-height: 1.55;
|
|
1990
|
+
white-space: pre-wrap;
|
|
1991
|
+
word-break: break-word;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
.dshcs-warn {
|
|
1995
|
+
margin: 8px 12px 0;
|
|
1996
|
+
padding: 6px 8px;
|
|
1997
|
+
border: 1px solid var(--vscode-inputValidation-warningBorder, #b89500);
|
|
1998
|
+
background: var(--vscode-inputValidation-warningBackground, rgba(184, 149, 0, 0.15));
|
|
1999
|
+
border-radius: 4px;
|
|
2000
|
+
font-size: 12px;
|
|
2001
|
+
white-space: pre-wrap;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
.dshcs-log { flex: 1; overflow-y: auto; padding: 12px 14px 4px; }
|
|
2005
|
+
|
|
2006
|
+
.dshcs-empty { opacity: 0.6; font-size: 12px; padding: 4px 0; }
|
|
2007
|
+
|
|
2008
|
+
/* ---- 消息条目:排版交给官方渲染器,这里只管气泡与间距 ---- */
|
|
2009
|
+
|
|
2010
|
+
.dshcs-msg { margin-bottom: 14px; }
|
|
2011
|
+
|
|
2012
|
+
.dshcs-user { display: flex; justify-content: flex-end; }
|
|
2013
|
+
|
|
2014
|
+
.dshcs-user-stack {
|
|
2015
|
+
max-width: 88%;
|
|
2016
|
+
display: flex;
|
|
2017
|
+
flex-direction: column;
|
|
2018
|
+
align-items: stretch;
|
|
2019
|
+
gap: 2px;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
.dshcs-context .dshcs-context-summary {
|
|
2023
|
+
min-width: 0;
|
|
2024
|
+
overflow: hidden;
|
|
2025
|
+
flex: 1 1 auto;
|
|
2026
|
+
color: var(--dsw-alias-label-tertiary, var(--vscode-descriptionForeground, rgba(128, 128, 128, 0.95)));
|
|
2027
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
2028
|
+
white-space: nowrap;
|
|
2029
|
+
text-overflow: ellipsis;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
.dshcs-context .dshcs-context-body {
|
|
2033
|
+
margin: 2px 0 4px;
|
|
2034
|
+
padding: 4px 0 4px calc(22px + var(--dsh-content-font-delta, 0px));
|
|
2035
|
+
color: var(--dsw-alias-label-tertiary, var(--vscode-descriptionForeground, rgba(128, 128, 128, 0.95)));
|
|
2036
|
+
font-size: var(--dsh-content-font-size-secondary, 12px);
|
|
2037
|
+
font-family: var(--ds-font-family-code, monospace);
|
|
2038
|
+
line-height: 1.5;
|
|
2039
|
+
white-space: pre-wrap;
|
|
2040
|
+
word-break: break-word;
|
|
2041
|
+
max-height: 40vh;
|
|
2042
|
+
overflow: auto;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
.dshcs-bubble {
|
|
2046
|
+
padding: 6px 10px;
|
|
2047
|
+
border-radius: 8px;
|
|
2048
|
+
background: var(--vscode-input-background, var(--dsw-alias-bg-layer-2, rgba(128, 128, 128, 0.12)));
|
|
2049
|
+
border: 1px solid var(--vscode-input-border, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.25)));
|
|
2050
|
+
white-space: pre-wrap;
|
|
2051
|
+
word-break: break-word;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
.dshcs-bubble[data-status="sending"] { opacity: 0.6; }
|
|
2055
|
+
|
|
2056
|
+
.dshcs-bubble[data-status="error"] {
|
|
2057
|
+
border-color: var(--vscode-inputValidation-errorBorder, #be1100);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
.dshcs-assistant { word-break: break-word; }
|
|
2061
|
+
|
|
2062
|
+
.dshcs-tool {
|
|
2063
|
+
display: flex;
|
|
2064
|
+
align-items: baseline;
|
|
2065
|
+
gap: 8px;
|
|
2066
|
+
margin: 4px 0 10px;
|
|
2067
|
+
padding: 4px 8px;
|
|
2068
|
+
border-left: 2px solid var(--vscode-panel-border, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4)));
|
|
2069
|
+
font-size: 12px;
|
|
2070
|
+
opacity: 0.85;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
.dshcs-tool .dshcs-tool-name { font-family: var(--ds-font-family-code, var(--vscode-editor-font-family, monospace)); }
|
|
2074
|
+
|
|
2075
|
+
.dshcs-tool .dshcs-tool-summary {
|
|
2076
|
+
flex: 1;
|
|
2077
|
+
overflow: hidden;
|
|
2078
|
+
text-overflow: ellipsis;
|
|
2079
|
+
white-space: nowrap;
|
|
2080
|
+
opacity: 0.8;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
.dshcs-tool .dshcs-tool-state[data-state="error"] {
|
|
2084
|
+
color: var(--vscode-errorForeground, #f48771);
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
/* ---- 授权卡片:桥里唯一的"非只读"操作,必须一眼看清 ---- */
|
|
2088
|
+
|
|
2089
|
+
.dshcs-approval {
|
|
2090
|
+
margin: 8px 12px;
|
|
2091
|
+
padding: 8px 10px;
|
|
2092
|
+
border: 1px solid var(--vscode-inputValidation-warningBorder, #b89500);
|
|
2093
|
+
background: var(--vscode-inputValidation-warningBackground, rgba(184, 149, 0, 0.12));
|
|
2094
|
+
border-radius: 6px;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
.dshcs-approval[data-decided] { opacity: 0.75; }
|
|
2098
|
+
|
|
2099
|
+
.dshcs-approval-head {
|
|
2100
|
+
display: flex;
|
|
2101
|
+
align-items: baseline;
|
|
2102
|
+
gap: 8px;
|
|
2103
|
+
font-size: 12px;
|
|
2104
|
+
margin-bottom: 4px;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
.dshcs-approval-title { font-weight: 600; }
|
|
2108
|
+
|
|
2109
|
+
.dshcs-approval-timer { flex: 1; text-align: right; opacity: 0.8; }
|
|
2110
|
+
|
|
2111
|
+
.dshcs-approval-body { font-size: 12px; margin-bottom: 6px; word-break: break-word; }
|
|
2112
|
+
|
|
2113
|
+
.dshcs-approval-tool { font-family: var(--ds-font-family-code, var(--vscode-editor-font-family, monospace)); }
|
|
2114
|
+
|
|
2115
|
+
.dshcs-approval-reason { margin-top: 2px; opacity: 0.9; white-space: pre-wrap; }
|
|
2116
|
+
|
|
2117
|
+
.dshcs-approval-actions {
|
|
2118
|
+
display: flex;
|
|
2119
|
+
align-items: center;
|
|
2120
|
+
gap: 8px;
|
|
2121
|
+
flex-wrap: wrap;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
.dshcs-approval-note { font-size: 12px; opacity: 0.85; }
|
|
2125
|
+
|
|
2126
|
+
/* ---- 状态行 + 输入框 ---- */
|
|
2127
|
+
|
|
2128
|
+
.dshcs-status {
|
|
2129
|
+
padding: 0 12px 6px;
|
|
2130
|
+
font-size: 12px;
|
|
2131
|
+
min-height: 16px;
|
|
2132
|
+
opacity: 0.85;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
.dshcs-status[data-status="error"] {
|
|
2136
|
+
color: var(--vscode-errorForeground, #f48771);
|
|
2137
|
+
opacity: 1;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
.dshcs-footer {
|
|
2141
|
+
display: flex;
|
|
2142
|
+
align-items: flex-end;
|
|
2143
|
+
gap: 8px;
|
|
2144
|
+
padding: 8px 12px;
|
|
2145
|
+
border-top: 1px solid var(--vscode-panel-border, var(--dsw-alias-border-l1, rgba(128, 128, 128, 0.35)));
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
.dshcs-footer textarea {
|
|
2149
|
+
flex: 1;
|
|
2150
|
+
min-height: 46px;
|
|
2151
|
+
max-height: 40vh;
|
|
2152
|
+
resize: vertical;
|
|
2153
|
+
box-sizing: border-box;
|
|
2154
|
+
padding: 6px 8px;
|
|
2155
|
+
border: 1px solid var(--vscode-input-border, var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35)));
|
|
2156
|
+
border-radius: 4px;
|
|
2157
|
+
color: var(--vscode-input-foreground, var(--dsw-alias-label-primary, inherit));
|
|
2158
|
+
background: var(--vscode-input-background, var(--dsw-alias-bg-layer-2, transparent));
|
|
2159
|
+
font-family: inherit;
|
|
2160
|
+
font-size: inherit;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
.dshcs-footer textarea:focus {
|
|
2164
|
+
outline: 1px solid var(--vscode-focusBorder, var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.7)));
|
|
2165
|
+
outline-offset: -1px;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
/* 降级路径的按钮 / 折叠行(拿不到官方 primitives 时才会渲染出来) */
|
|
2169
|
+
|
|
2170
|
+
.dshcs-btn {
|
|
2171
|
+
padding: 5px 12px;
|
|
2172
|
+
border: 1px solid var(--vscode-button-border, transparent);
|
|
2173
|
+
border-radius: 4px;
|
|
2174
|
+
cursor: pointer;
|
|
2175
|
+
color: var(--vscode-button-foreground, #fff);
|
|
2176
|
+
background: var(--vscode-button-background, var(--dsw-alias-button-contrast-fill, #4176e6));
|
|
2177
|
+
font-family: inherit;
|
|
2178
|
+
font-size: 12px;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
.dshcs-btn-secondary {
|
|
2182
|
+
color: var(--vscode-button-secondaryForeground, var(--dsw-alias-label-primary, #ccc));
|
|
2183
|
+
background: var(--vscode-button-secondaryBackground, var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.2)));
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
.dshcs-btn:disabled { opacity: 0.5; cursor: default; }
|
|
2187
|
+
|
|
2188
|
+
.dshcs-row-toggle {
|
|
2189
|
+
border: none;
|
|
2190
|
+
background: transparent;
|
|
2191
|
+
color: inherit;
|
|
2192
|
+
cursor: pointer;
|
|
2193
|
+
padding: 0 0 2px;
|
|
2194
|
+
font: inherit;
|
|
2195
|
+
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
2196
|
+
opacity: 0.9;
|
|
2197
|
+
text-align: left;
|
|
2198
|
+
}
|
|
2199
|
+
`
|
|
2200
|
+
|
|
2201
|
+
// ---- 面板组件(手写;等价于 0.3.58 之前 webview/src 下那三个 .jsx)----
|
|
2202
|
+
|
|
2203
|
+
/** 官方渲染器的界面文案(面板自己给中文;DSH 界面走它自己的 locale)。
|
|
2204
|
+
* **必须是常量**:MarkdownText 的流式缓存按 labels 的引用身份判失效,每帧新建会一直重解析。 */
|
|
2205
|
+
var ASK_LABELS = { code: { copyLabel: '复制', copiedLabel: '已复制' }, footnotes: '脚注' }
|
|
2206
|
+
|
|
2207
|
+
/** 工具状态 / 授权状态 → 中文。 */
|
|
2208
|
+
var ASK_TOOL_STATE = { running: '运行中', ok: '完成', error: '失败' }
|
|
2209
|
+
var ASK_APPROVAL_STATE = {
|
|
2210
|
+
asked: '等待授权', 'allowed-once': '已允许一次', rejected: '已拒绝', cancelled: '已取消', unavailable: '无人处理',
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
/** 折叠摘要取首行(还在流式时取最新一行)—— 与官方 ReasoningRow 同一规则。 */
|
|
2214
|
+
function askSummaryLine(text, running) {
|
|
2215
|
+
var source = running ? String(text).replace(/\s+$/, '') : String(text)
|
|
2216
|
+
if (source === '') return ''
|
|
2217
|
+
var index = running ? source.lastIndexOf('\n') : source.indexOf('\n')
|
|
2218
|
+
var line = index === -1 ? source : (running ? source.slice(index + 1) : source.slice(0, index))
|
|
2219
|
+
return line.split('**').join('')
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
/** 剩余时间文案(超过一分钟 mm:ss:秒数跳动会让人紧张)。 */
|
|
2223
|
+
function askRemainingText(item, holdMs, now) {
|
|
2224
|
+
var at = Number.isSafeInteger(item.at) ? item.at : now
|
|
2225
|
+
var left = Math.max(0, at + holdMs - now)
|
|
2226
|
+
if (left === 0) return '已交回 DSH 界面'
|
|
2227
|
+
var totalSeconds = Math.ceil(left / 1000)
|
|
2228
|
+
var minutes = Math.floor(totalSeconds / 60)
|
|
2229
|
+
var seconds = totalSeconds % 60
|
|
2230
|
+
return minutes > 0
|
|
2231
|
+
? minutes + ' 分 ' + String(seconds).padStart(2, '0') + ' 秒后交回 DSH 界面'
|
|
2232
|
+
: seconds + ' 秒后交回 DSH 界面'
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
/**
|
|
2236
|
+
* 一行「思考」/「上下文」(默认收起,点行即展开)—— 外形与交互照抄官方 ReasoningRow。
|
|
2237
|
+
*
|
|
2238
|
+
* 官方部件在就用官方的(DisclosureRow + 官方图标);拿不到就退成一个朴素的按钮行:
|
|
2239
|
+
* **能看、能点、能展开**,只是没有那套图标与排版。
|
|
2240
|
+
*/
|
|
2241
|
+
function AskDisclosureRow(props) {
|
|
2242
|
+
var state = React.useState(false)
|
|
2243
|
+
var expanded = state[0]
|
|
2244
|
+
var setExpanded = state[1]
|
|
2245
|
+
var primitives = askPrimitives().primitives
|
|
2246
|
+
var Row = askComponent(primitives, 'DisclosureRow')
|
|
2247
|
+
var Icon = primitives !== null && typeof props.iconName === 'string' ? askComponent(primitives, props.iconName) : null
|
|
2248
|
+
|
|
2249
|
+
var collapsed = React.createElement(
|
|
2250
|
+
React.Fragment,
|
|
2251
|
+
null,
|
|
2252
|
+
props.separator === true
|
|
2253
|
+
? React.createElement('span', { className: 'dshcs-think-separator', 'aria-hidden': true })
|
|
2254
|
+
: null,
|
|
2255
|
+
React.createElement('span', { className: props.summaryClass }, props.summaryText),
|
|
2256
|
+
)
|
|
2257
|
+
var body = React.createElement(props.bodyTag, { className: props.bodyClass }, props.bodyText)
|
|
2258
|
+
|
|
2259
|
+
if (Row === null) {
|
|
2260
|
+
return React.createElement(
|
|
2261
|
+
'div',
|
|
2262
|
+
{ className: props.rootClass, 'data-expanded': expanded || undefined },
|
|
2263
|
+
React.createElement(
|
|
2264
|
+
'button',
|
|
2265
|
+
{ type: 'button', className: 'dshcs-row-toggle', onClick: function () { setExpanded(!expanded) } },
|
|
2266
|
+
(expanded ? '▾ ' : '▸ ') + props.title,
|
|
2267
|
+
),
|
|
2268
|
+
collapsed,
|
|
2269
|
+
expanded ? body : null,
|
|
2270
|
+
)
|
|
2271
|
+
}
|
|
2272
|
+
return React.createElement(
|
|
2273
|
+
'div',
|
|
2274
|
+
{ className: props.rootClass, 'data-expanded': expanded || undefined, 'data-state': props.running === true ? 'running' : 'ok' },
|
|
2275
|
+
React.createElement(
|
|
2276
|
+
Row,
|
|
2277
|
+
{
|
|
2278
|
+
icon: Icon === null ? null : React.createElement(Icon, props.iconSize === 16 ? { size: 16 } : { size: 14 }),
|
|
2279
|
+
title: props.title,
|
|
2280
|
+
open: expanded,
|
|
2281
|
+
expandable: true,
|
|
2282
|
+
expandOnRowClick: true,
|
|
2283
|
+
onToggle: function () { setExpanded(!expanded) },
|
|
2284
|
+
collapsedContent: collapsed,
|
|
2285
|
+
},
|
|
2286
|
+
body,
|
|
2287
|
+
),
|
|
2288
|
+
)
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
/** 官方按钮(拿不到就退成原生 button,并打上 .dshcs-btn)。 */
|
|
2292
|
+
function askButton(props, text) {
|
|
2293
|
+
var primitives = askPrimitives().primitives
|
|
2294
|
+
var Button = askComponent(primitives, 'Button')
|
|
2295
|
+
if (Button === null) {
|
|
2296
|
+
return React.createElement('button', {
|
|
2297
|
+
type: 'button',
|
|
2298
|
+
className: props.secondary === true ? 'dshcs-btn dshcs-btn-secondary' : 'dshcs-btn',
|
|
2299
|
+
disabled: props.disabled === true,
|
|
2300
|
+
onClick: props.onClick,
|
|
2301
|
+
}, text)
|
|
2302
|
+
}
|
|
2303
|
+
return React.createElement(Button, {
|
|
2304
|
+
variant: props.secondary === true ? 'outline' : 'primary',
|
|
2305
|
+
size: 'sm',
|
|
2306
|
+
disabled: props.disabled === true,
|
|
2307
|
+
onClick: props.onClick,
|
|
2308
|
+
}, text)
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
/**
|
|
2312
|
+
* 一个对话条目(等价于 thread.jsx 的 ThreadEntry)。
|
|
2313
|
+
*
|
|
2314
|
+
* 助手正文**不自己排版**:交给官方 `MarkdownText`(与 DSH 界面同一份:micromark/mdast 管线、
|
|
2315
|
+
* shiki 高亮、KaTeX、增量流式解析)。拿不到官方渲染器时才退回 <pre>(可读,但不假装排版)。
|
|
2316
|
+
*/
|
|
2317
|
+
function AskThreadEntry(props) {
|
|
2318
|
+
var entry = props.entry
|
|
2319
|
+
if (entry === null || typeof entry !== 'object') return null
|
|
2320
|
+
var text = typeof entry.text === 'string' ? entry.text : ''
|
|
2321
|
+
|
|
2322
|
+
if (entry.role === 'user') {
|
|
2323
|
+
// 别的工具注入的上下文不是"用户说的"(0.3.43):按官方的「上下文注入」折叠显示。
|
|
2324
|
+
if (typeof entry.sourceKind === 'string' && entry.sourceKind !== '') {
|
|
2325
|
+
return React.createElement(
|
|
2326
|
+
'div',
|
|
2327
|
+
{ className: 'dshcs-msg dshcs-injection' },
|
|
2328
|
+
React.createElement(AskDisclosureRow, {
|
|
2329
|
+
rootClass: 'dshcs-context', title: '上下文注入', iconName: 'IconContextInjectionOutline16', iconSize: 16,
|
|
2330
|
+
summaryText: String(text).split('\n')[0].replace(/^From the editor:\s*/i, ''),
|
|
2331
|
+
summaryClass: 'dshcs-context-summary', bodyClass: 'dshcs-context-body', bodyTag: 'pre', bodyText: text,
|
|
2332
|
+
}),
|
|
2333
|
+
)
|
|
2334
|
+
}
|
|
2335
|
+
var context = typeof entry.context === 'string' ? entry.context : ''
|
|
2336
|
+
return React.createElement(
|
|
2337
|
+
'div',
|
|
2338
|
+
{ className: 'dshcs-msg dshcs-user' },
|
|
2339
|
+
React.createElement(
|
|
2340
|
+
'div',
|
|
2341
|
+
{ className: 'dshcs-user-stack' },
|
|
2342
|
+
context === '' ? null : React.createElement(AskDisclosureRow, {
|
|
2343
|
+
rootClass: 'dshcs-context', title: '上下文', iconName: 'IconContextInjectionOutline16', iconSize: 16,
|
|
2344
|
+
summaryText: context.split('\n')[0].replace(/^From the editor:\s*/i, ''),
|
|
2345
|
+
summaryClass: 'dshcs-context-summary', bodyClass: 'dshcs-context-body', bodyTag: 'pre', bodyText: context,
|
|
2346
|
+
}),
|
|
2347
|
+
React.createElement('div', {
|
|
2348
|
+
className: 'dshcs-bubble',
|
|
2349
|
+
'data-status': entry.status === undefined || entry.status === null ? undefined : entry.status,
|
|
2350
|
+
}, text),
|
|
2351
|
+
),
|
|
2352
|
+
)
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
if (entry.role === 'assistant') {
|
|
2356
|
+
var thinking = typeof entry.thinking === 'string' ? entry.thinking : ''
|
|
2357
|
+
// 正文还没到(整轮都是思考)→ 思考行标"思考中";正文到了就标"思考"(官方同一判据)。
|
|
2358
|
+
var running = entry.streaming === true && text === ''
|
|
2359
|
+
var primitives = askPrimitives().primitives
|
|
2360
|
+
var Markdown = askComponent(primitives, 'MarkdownText')
|
|
2361
|
+
var bodyElement = text === ''
|
|
2362
|
+
? null
|
|
2363
|
+
: (Markdown === null
|
|
2364
|
+
? React.createElement('pre', { className: 'dshcs-md-fallback' }, text)
|
|
2365
|
+
: React.createElement(Markdown, { text: text, streaming: entry.streaming === true, labels: ASK_LABELS }))
|
|
2366
|
+
return React.createElement(
|
|
2367
|
+
'div',
|
|
2368
|
+
{ className: 'dshcs-msg dshcs-assistant' },
|
|
2369
|
+
thinking === '' ? null : React.createElement(AskDisclosureRow, {
|
|
2370
|
+
rootClass: 'dshcs-think', title: running ? '思考中' : '思考', iconName: 'IconThinkOutline14', iconSize: 14,
|
|
2371
|
+
running: running, separator: true, summaryText: askSummaryLine(thinking, running),
|
|
2372
|
+
summaryClass: 'dshcs-think-summary', bodyClass: 'dshcs-think-body', bodyTag: 'div', bodyText: thinking,
|
|
2373
|
+
}),
|
|
2374
|
+
bodyElement,
|
|
2375
|
+
)
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
if (entry.role === 'tool') {
|
|
2379
|
+
return React.createElement(
|
|
2380
|
+
'div',
|
|
2381
|
+
{ className: 'dshcs-tool', 'data-state': entry.status === undefined || entry.status === null ? 'running' : entry.status },
|
|
2382
|
+
React.createElement('span', { className: 'dshcs-tool-name' }, entry.name === undefined || entry.name === null ? 'tool' : entry.name),
|
|
2383
|
+
React.createElement('span', { className: 'dshcs-tool-summary' }, entry.summary === undefined || entry.summary === null ? '' : entry.summary),
|
|
2384
|
+
React.createElement(
|
|
2385
|
+
'span',
|
|
2386
|
+
{ className: 'dshcs-tool-state', 'data-state': entry.status === undefined || entry.status === null ? 'running' : entry.status },
|
|
2387
|
+
ASK_TOOL_STATE[entry.status] !== undefined ? ASK_TOOL_STATE[entry.status] : (entry.status === undefined || entry.status === null ? '' : entry.status),
|
|
2388
|
+
),
|
|
2389
|
+
)
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
if (entry.role === 'approval') {
|
|
2393
|
+
var state = entry.status === undefined || entry.status === null ? 'asked' : entry.status
|
|
2394
|
+
return React.createElement(
|
|
2395
|
+
'div',
|
|
2396
|
+
{ className: 'dshcs-tool dshcs-approval-row', 'data-state': state },
|
|
2397
|
+
React.createElement('span', { className: 'dshcs-tool-name' }, entry.toolName !== undefined && entry.toolName !== null ? entry.toolName : (entry.name !== undefined && entry.name !== null ? entry.name : 'tool')),
|
|
2398
|
+
React.createElement('span', { className: 'dshcs-tool-summary' }, entry.summary === undefined || entry.summary === null ? '' : entry.summary),
|
|
2399
|
+
React.createElement('span', { className: 'dshcs-tool-state' }, ASK_APPROVAL_STATE[state] !== undefined ? ASK_APPROVAL_STATE[state] : state),
|
|
2400
|
+
)
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
return null
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
/**
|
|
2407
|
+
* 待决授权卡片(等价于 approval.jsx)。
|
|
2408
|
+
*
|
|
2409
|
+
* 两条实测教训都在这里:卡片**不自己判过期**(按钮能不能点由"宿主还挂着这条请求"决定,
|
|
2410
|
+
* 请求被交回官方链路时它会从 approvals 里消失 → 变成审计行),并且只对这一次动作有效,
|
|
2411
|
+
* 没有"以后都允许"这种入口。
|
|
2412
|
+
*/
|
|
2413
|
+
function AskApprovalCard(props) {
|
|
2414
|
+
var item = props.item
|
|
2415
|
+
var choice = props.decided === undefined ? null : props.decided
|
|
2416
|
+
var locked = choice !== null
|
|
2417
|
+
return React.createElement(
|
|
2418
|
+
'div',
|
|
2419
|
+
{ className: 'dshcs-approval', 'data-decided': choice === null ? undefined : choice },
|
|
2420
|
+
React.createElement(
|
|
2421
|
+
'div',
|
|
2422
|
+
{ className: 'dshcs-approval-head' },
|
|
2423
|
+
React.createElement('span', { className: 'dshcs-approval-title' }, '需要你的授权'),
|
|
2424
|
+
React.createElement('span', { className: 'dshcs-approval-timer' }, askRemainingText(item, props.holdMs, props.now)),
|
|
2425
|
+
),
|
|
2426
|
+
React.createElement(
|
|
2427
|
+
'div',
|
|
2428
|
+
{ className: 'dshcs-approval-body' },
|
|
2429
|
+
React.createElement('span', { className: 'dshcs-approval-tool' }, item.toolName),
|
|
2430
|
+
item.reason === '' || item.reason === undefined ? null : React.createElement('div', { className: 'dshcs-approval-reason' }, item.reason),
|
|
2431
|
+
),
|
|
2432
|
+
React.createElement(
|
|
2433
|
+
'div',
|
|
2434
|
+
{ className: 'dshcs-approval-actions' },
|
|
2435
|
+
askButton({ disabled: locked, onClick: function () { props.onDecide(item.id, 'allowed-once') } }, '允许一次'),
|
|
2436
|
+
askButton({ secondary: true, disabled: locked, onClick: function () { props.onDecide(item.id, 'rejected') } }, '拒绝'),
|
|
2437
|
+
React.createElement(
|
|
2438
|
+
'span',
|
|
2439
|
+
{ className: 'dshcs-approval-note' },
|
|
2440
|
+
choice === null
|
|
2441
|
+
? '只对这一次动作有效;关掉面板即交回 DSH 界面'
|
|
2442
|
+
: (choice === 'allowed-once' ? '已允许一次(DSH 继续执行)' : '已拒绝'),
|
|
2443
|
+
),
|
|
2444
|
+
),
|
|
2445
|
+
)
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
/** 面板本体:**纯展示** —— 状态全部由 AskRoot 通过 props 给进来,可以直接喂一棵假 view 单测。 */
|
|
2449
|
+
function AskPanel(props) {
|
|
2450
|
+
var view = props.view
|
|
2451
|
+
var entries = view.entries
|
|
2452
|
+
// 状态行优先级:面板自己的脚本错误 > 宿主给的 statusText > 客户端侧的投递提示(排队/插入当前轮)。
|
|
2453
|
+
var status = props.scriptError !== null
|
|
2454
|
+
? props.scriptError
|
|
2455
|
+
: (view.statusText !== '' ? view.statusText : (view.deliveryNote === null ? '' : view.deliveryNote))
|
|
2456
|
+
return React.createElement(
|
|
2457
|
+
'div',
|
|
2458
|
+
{ className: 'dshcs-app' },
|
|
2459
|
+
React.createElement(
|
|
2460
|
+
'header',
|
|
2461
|
+
{ className: 'dshcs-header' },
|
|
2462
|
+
React.createElement('span', { className: 'dshcs-title' }, 'DSH'),
|
|
2463
|
+
React.createElement('span', { className: 'dshcs-where' }, view.contextText),
|
|
2464
|
+
React.createElement('button', {
|
|
2465
|
+
type: 'button', className: 'dshcs-close', title: '关闭(Shift+Esc)', 'aria-label': '关闭', onClick: props.onClose,
|
|
2466
|
+
}, '✕'),
|
|
2467
|
+
),
|
|
2468
|
+
props.scriptError === null ? null : React.createElement('div', { className: 'dshcs-warn' }, props.scriptError),
|
|
2469
|
+
view.threadError === null ? null : React.createElement('div', { className: 'dshcs-warn' }, view.threadError),
|
|
2470
|
+
React.createElement(
|
|
2471
|
+
'main',
|
|
2472
|
+
{ className: 'dshcs-log', ref: props.logRef, onScroll: props.onScroll },
|
|
2473
|
+
entries.length === 0
|
|
2474
|
+
? React.createElement('div', { className: 'dshcs-empty' }, '在下面提问:DSH 的回答会像 DSH 界面那样显示在这里(思考折叠、正文按官方渲染)。')
|
|
2475
|
+
: entries.map(function (entry, index) {
|
|
2476
|
+
return React.createElement(AskThreadEntry, {
|
|
2477
|
+
key: String(entry.role) + '-' + index + '-' + (entry.callId !== undefined && entry.callId !== null ? entry.callId : (entry.approvalId !== undefined && entry.approvalId !== null ? entry.approvalId : '')),
|
|
2478
|
+
entry: entry,
|
|
2479
|
+
})
|
|
2480
|
+
}),
|
|
2481
|
+
),
|
|
2482
|
+
view.approvals.map(function (item) {
|
|
2483
|
+
return React.createElement(AskApprovalCard, {
|
|
2484
|
+
key: item.id, item: item, holdMs: view.approvalHoldMs, now: props.now,
|
|
2485
|
+
decided: props.decided[item.id] === undefined ? null : props.decided[item.id],
|
|
2486
|
+
onDecide: props.onDecide,
|
|
2487
|
+
})
|
|
2488
|
+
}),
|
|
2489
|
+
React.createElement('div', { className: 'dshcs-status', 'data-status': view.status }, status),
|
|
2490
|
+
React.createElement(
|
|
2491
|
+
'footer',
|
|
2492
|
+
{ className: 'dshcs-footer' },
|
|
2493
|
+
React.createElement('textarea', {
|
|
2494
|
+
value: props.draft,
|
|
2495
|
+
placeholder: '问 DSH…(Enter 发送,Shift+Enter 换行)',
|
|
2496
|
+
onChange: function (event) { props.onDraft(event.target.value) },
|
|
2497
|
+
onKeyDown: props.onKeyDown,
|
|
2498
|
+
autoFocus: true,
|
|
2499
|
+
}),
|
|
2500
|
+
askButton({ disabled: props.sending === true || String(props.draft).trim() === '', onClick: props.onSubmit }, '发送'),
|
|
2501
|
+
),
|
|
2502
|
+
)
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
/** 渲染兜底:面板渲染抛错时显示原因,而不是留一块空白(空白是最难查的失败)。 */
|
|
2506
|
+
function askErrorView(error) {
|
|
2507
|
+
var message = error !== null && error !== undefined && error.message !== undefined ? error.message : String(error)
|
|
2508
|
+
return React.createElement('div', { className: 'dshcs-panel' },
|
|
2509
|
+
React.createElement('div', { className: 'dshcs-warn' }, '面板渲染出错:' + message))
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
/** 错误边界(React 只在类组件里提供它)。 */
|
|
2513
|
+
class AskBoundary extends React.Component {
|
|
2514
|
+
constructor(props) {
|
|
2515
|
+
super(props)
|
|
2516
|
+
this.state = { error: null }
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
componentDidCatch(error) {
|
|
2520
|
+
console.error('[code-server] 「问 DSH」面板渲染出错:', error)
|
|
2521
|
+
this.setState({ error: error })
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
render() {
|
|
2525
|
+
if (this.state.error !== null) return askErrorView(this.state.error)
|
|
2526
|
+
return this.props.children === undefined ? null : this.props.children
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
/** 面板的根:持有视图状态,并把宿主的每趟状态推进来。 */
|
|
2531
|
+
function AskRoot() {
|
|
2532
|
+
var viewState = React.useState(askViewState())
|
|
2533
|
+
var view = viewState[0]
|
|
2534
|
+
var setView = viewState[1]
|
|
2535
|
+
var nowState = React.useState(function () { return Date.now() })
|
|
2536
|
+
var now = nowState[0]
|
|
2537
|
+
var setNow = nowState[1]
|
|
2538
|
+
var draftState = React.useState('')
|
|
2539
|
+
var sendingState = React.useState(false)
|
|
2540
|
+
var errorState = React.useState(null)
|
|
2541
|
+
var decidedState = React.useState({})
|
|
2542
|
+
var draft = draftState[0]
|
|
2543
|
+
var setDraft = draftState[1]
|
|
2544
|
+
var sending = sendingState[0]
|
|
2545
|
+
var setSending = sendingState[1]
|
|
2546
|
+
var scriptError = errorState[0]
|
|
2547
|
+
var decided = decidedState[0]
|
|
2548
|
+
var setDecided = decidedState[1]
|
|
2549
|
+
var logRef = React.useRef(null)
|
|
2550
|
+
var stickRef = React.useRef(true)
|
|
2551
|
+
|
|
2552
|
+
// 宿主 → 面板:壳直接调(同一个 window,不走 postMessage —— DSH 页面里 postMessage 是公共广播)。
|
|
2553
|
+
React.useEffect(function () {
|
|
2554
|
+
ask.push = function (payload) {
|
|
2555
|
+
if (payload === null || typeof payload !== 'object') return
|
|
2556
|
+
setView(function (prev) { return askApplyPayload(prev, Object.assign({}, payload, { type: 'state' })) })
|
|
2557
|
+
}
|
|
2558
|
+
if (ask.pending !== null) ask.push(ask.pending)
|
|
2559
|
+
return function () { ask.push = null }
|
|
2560
|
+
}, [])
|
|
2561
|
+
|
|
2562
|
+
// 授权倒计时:只在有卡片时跑。
|
|
2563
|
+
React.useEffect(function () {
|
|
2564
|
+
if (view.approvals.length === 0) return undefined
|
|
2565
|
+
var timer = window.setInterval(function () { setNow(Date.now()) }, 250)
|
|
2566
|
+
return function () { window.clearInterval(timer) }
|
|
2567
|
+
}, [view.approvals.length])
|
|
2568
|
+
|
|
2569
|
+
// 已提交的决策:卡片消失后清掉(不然 decided 会一直涨)。
|
|
2570
|
+
React.useEffect(function () {
|
|
2571
|
+
setDecided(function (prev) {
|
|
2572
|
+
var ids = {}
|
|
2573
|
+
view.approvals.forEach(function (item) { ids[item.id] = true })
|
|
2574
|
+
var next = {}
|
|
2575
|
+
var changed = false
|
|
2576
|
+
Object.keys(prev).forEach(function (id) {
|
|
2577
|
+
if (ids[id] === true) next[id] = prev[id]
|
|
2578
|
+
else changed = true
|
|
2579
|
+
})
|
|
2580
|
+
return changed ? next : prev
|
|
2581
|
+
})
|
|
2582
|
+
}, [view.approvals])
|
|
2583
|
+
|
|
2584
|
+
// 贴底滚动:用户主动往上翻时不打扰。
|
|
2585
|
+
React.useEffect(function () {
|
|
2586
|
+
var node = logRef.current
|
|
2587
|
+
if (node !== null && node !== undefined && stickRef.current) node.scrollTop = node.scrollHeight
|
|
2588
|
+
}, [view.entries])
|
|
2589
|
+
|
|
2590
|
+
// 任何一趟宿主状态回来都解除"发送中"的按钮锁(0.3.44 的教训:对话框形态下宿主的 status
|
|
2591
|
+
// **一直是 idle**,盯着 status 变化的写法会让按钮第一次发送后永久变灰)。
|
|
2592
|
+
React.useEffect(function () { setSending(false) }, [view])
|
|
2593
|
+
// 兜底:某一趟状态没回来(宿主卡住/请求失败)时最多 15 秒也解锁。
|
|
2594
|
+
React.useEffect(function () {
|
|
2595
|
+
if (sending !== true) return undefined
|
|
2596
|
+
var timer = window.setTimeout(function () { setSending(false) }, 15000)
|
|
2597
|
+
return function () { window.clearTimeout(timer) }
|
|
2598
|
+
}, [sending])
|
|
2599
|
+
|
|
2600
|
+
var submit = function () {
|
|
2601
|
+
var text = String(draft).trim()
|
|
2602
|
+
if (text === '' || sending) return
|
|
2603
|
+
setSending(true)
|
|
2604
|
+
setDraft('')
|
|
2605
|
+
askOnMessage({ type: 'ask', text: text })
|
|
2606
|
+
}
|
|
2607
|
+
var onKeyDown = function (event) {
|
|
2608
|
+
if (event.key === 'Enter' && event.shiftKey === false) {
|
|
2609
|
+
event.preventDefault()
|
|
2610
|
+
submit()
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
var onScroll = function () {
|
|
2614
|
+
var node = logRef.current
|
|
2615
|
+
if (node === null || node === undefined) return
|
|
2616
|
+
stickRef.current = node.scrollHeight - node.scrollTop - node.clientHeight < 48
|
|
2617
|
+
}
|
|
2618
|
+
var onDecide = function (id, outcome) {
|
|
2619
|
+
setDecided(function (prev) {
|
|
2620
|
+
var next = Object.assign({}, prev)
|
|
2621
|
+
next[id] = outcome
|
|
2622
|
+
return next
|
|
2623
|
+
})
|
|
2624
|
+
askOnMessage({ type: 'approve', id: id, outcome: outcome })
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
return React.createElement(
|
|
2628
|
+
AskBoundary,
|
|
2629
|
+
null,
|
|
2630
|
+
React.createElement(AskPanel, {
|
|
2631
|
+
view: view, now: now, draft: draft, sending: sending, scriptError: scriptError, decided: decided,
|
|
2632
|
+
logRef: logRef, onScroll: onScroll, onDraft: setDraft, onKeyDown: onKeyDown,
|
|
2633
|
+
onSubmit: submit, onDecide: onDecide, onClose: function () { askOnMessage({ type: 'close' }) },
|
|
2634
|
+
}),
|
|
2635
|
+
)
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
/** 挂载面板(只做一次):样式进 <head>,React 根挂在对话框的 body 上。 */
|
|
2639
|
+
function askMount() {
|
|
2640
|
+
if (ask.mounted) return
|
|
2641
|
+
askEnsureShell()
|
|
2642
|
+
if (document.getElementById('dshcs-ask-style') === null) {
|
|
2643
|
+
var style = document.createElement('style')
|
|
2644
|
+
style.id = 'dshcs-ask-style'
|
|
2645
|
+
style.textContent = ASK_CSS
|
|
2646
|
+
document.head.appendChild(style)
|
|
2647
|
+
}
|
|
2648
|
+
var renderer = askPrimitives()
|
|
2649
|
+
if (renderer.createRoot === null) {
|
|
2650
|
+
// 连 React 都取不到:至少把"为什么"写在面板里,不要开一扇空白窗。
|
|
2651
|
+
var why = document.createElement('div')
|
|
2652
|
+
why.className = 'dshcs-warn'
|
|
2653
|
+
why.textContent = '面板无法挂载:模块表里取不到 react-dom/client(' + renderer.why + ')'
|
|
2654
|
+
ask.body.appendChild(why)
|
|
2655
|
+
askNotice('面板无法挂载:' + renderer.why)
|
|
2656
|
+
ask.mounted = true
|
|
2657
|
+
return
|
|
2658
|
+
}
|
|
2659
|
+
ask.root = renderer.createRoot(ask.body)
|
|
2660
|
+
ask.root.render(React.createElement(AskBoundary, null, React.createElement(AskRoot, null)))
|
|
2661
|
+
ask.mounted = true
|
|
2662
|
+
console.log('[code-server] 「问 DSH」面板已挂载'
|
|
2663
|
+
+ (renderer.primitives === null ? '(降级:无官方渲染器)' : '(官方渲染器:页面模块表)'))
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
/** 轮询 host 状态:open 就显示并推状态,关了或没变化就什么都不做。 */
|
|
2667
|
+
function askPoll() {
|
|
2668
|
+
api('/code-server/ask/state?rev=' + ask.rev).then(function (result) {
|
|
2669
|
+
if (result == null || result.ok !== true) return
|
|
2670
|
+
if (result.changed !== true) return
|
|
2671
|
+
ask.rev = typeof result.rev === 'number' ? result.rev : -1
|
|
2672
|
+
ask.pending = result
|
|
2673
|
+
if (result.open === true) {
|
|
2674
|
+
askEnsureShell()
|
|
2675
|
+
ask.el.style.display = 'block'
|
|
2676
|
+
if (ask.title !== null) ask.title.textContent = result.contextText || '来自编辑器'
|
|
2677
|
+
ask.open = true
|
|
2678
|
+
askMount()
|
|
2679
|
+
if (ask.mounted) askPush(true)
|
|
2680
|
+
} else if (ask.el !== null) {
|
|
2681
|
+
ask.el.style.display = 'none'
|
|
2682
|
+
ask.open = false
|
|
2683
|
+
}
|
|
2684
|
+
}).catch(function () { /* 宿主没起/旧版:静默重试 */ })
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
function askStart() {
|
|
2688
|
+
if (ask.timer !== null) return
|
|
2689
|
+
ask.timer = setInterval(askPoll, ASK_POLL_MS)
|
|
2690
|
+
setTimeout(askPoll, 800)
|
|
2691
|
+
// 0.3.42 的"空闲预热面板产物"随注入机制一起去掉了:面板现在就是本文件里的组件,
|
|
2692
|
+
// 页面加载插件时已经解析完,没有 1.4MB 的"取文本 + 解析 + 执行"要等。
|
|
2693
|
+
console.log('[code-server] ask dialog poller started (every ' + ASK_POLL_MS + 'ms)')
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
function apply(ctx) {
|
|
2697
|
+
try {
|
|
2698
|
+
internalApply(ctx)
|
|
2699
|
+
askStart()
|
|
2700
|
+
} catch (err) {
|
|
2701
|
+
console.error('[code-server] apply failed:', err && err.stack ? err.stack : String(err))
|
|
2702
|
+
try { document.title = 'CS-ERR ' + ((err && err.message) || String(err)) } catch (e) { /* ignore */ }
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
function internalApply(ctx) {
|
|
2706
|
+
var slots = ctx.get('slots')
|
|
2707
|
+
if (slots === undefined) {
|
|
2708
|
+
console.error('[code-server] slots service unavailable')
|
|
2709
|
+
return
|
|
2710
|
+
}
|
|
2711
|
+
// settings scope 提前绑定(设置卡共用)
|
|
2712
|
+
var settingsScope = ctx.settingsScope
|
|
2713
|
+
var scope = null
|
|
2714
|
+
if (settingsScope !== undefined && typeof settingsScope.bind === 'function') {
|
|
2715
|
+
scope = settingsScope.bind({ namespace: 'code-server' })
|
|
2716
|
+
} else {
|
|
2717
|
+
console.error('[code-server] settingsScope unavailable (inject missing?); settings card disabled')
|
|
2718
|
+
return
|
|
2719
|
+
}
|
|
2720
|
+
// 预拉取 status(设置卡/预热/侧栏 body 共用)
|
|
2721
|
+
api('/code-server/status').then(function (s) {
|
|
2722
|
+
setState({ status: s })
|
|
2723
|
+
}).catch(function () { /* 首次失败由后续轮询补救 */ })
|
|
2724
|
+
|
|
2725
|
+
// ---- 设置:两条腿都注册,谁被声明谁生效(两次注册不会同时出现) ----
|
|
2726
|
+
// ① 插件页的配置区(DSH ≥ 0.1.6-alpha.2):`settings.plugin.item` 退役,上游把插件配置搬到
|
|
2727
|
+
// 插件页 —— 键是**包名**(页面按 `pkg.name` 派发),只问 `view: 'page'`。插件页只在
|
|
2728
|
+
// `ledger.bundles.has(包名)` 时才渲染那一块,所以键写错就等于"设置又不见了"。
|
|
2729
|
+
slots.inject('plugins.bundle.config', () => slots.register(
|
|
2730
|
+
{ name: 'plugins.bundle.config', key: PKG_NAME, inject: function () { return { scope: scope } } },
|
|
2731
|
+
BundleConfigEntry
|
|
2732
|
+
))
|
|
2733
|
+
// ② 设置页里的插件卡片(DSH ≤ 0.1.6-alpha.1;也是极旧 DSH 里唯一的提示出口)。
|
|
2734
|
+
// `slots.inject` 只在插槽真被声明时才回调 ⇒ 新 DSH 上这一条自然不生效。
|
|
2735
|
+
slots.inject('settings.plugin.item', () => slots.register(
|
|
2736
|
+
{ name: 'settings.plugin.item', key: 'code-server', label: 'Code Server', inject: function () { return { scope: scope } } },
|
|
2737
|
+
csSettingsPage
|
|
2738
|
+
))
|
|
2739
|
+
|
|
2740
|
+
// ---- 能力探测:只有带右侧栏服务(DSH ≥ 0.1.5-alpha.1)才注册可用 UI;探测不到就只留设置页提示 ----
|
|
2741
|
+
var modernSettled = false
|
|
2742
|
+
|
|
2743
|
+
function onModernUi(sctx, via) {
|
|
2744
|
+
if (modernSettled) return
|
|
2745
|
+
modernSettled = true
|
|
2746
|
+
clearTimers()
|
|
2747
|
+
var registered = false
|
|
2748
|
+
try {
|
|
2749
|
+
registered = registerSidebarTab(sctx)
|
|
2750
|
+
} catch (e) {
|
|
2751
|
+
console.error('[code-server] sidebar tab registration failed:',
|
|
2752
|
+
e != null && e.message != null ? e.message : String(e))
|
|
2753
|
+
}
|
|
2754
|
+
if (state.sidebarUi === 'legacy') {
|
|
2755
|
+
// 自愈:旧版判定之后服务才出现(慢启动的宿主)→ 撤销判定,并让 host 恢复预启动逻辑
|
|
2756
|
+
console.log('[code-server] 右侧栏服务晚到(经 ' + via + '):撤销旧版 DSH 判定')
|
|
2757
|
+
api('/code-server/ui-mode', { sidebar: true }).catch(function () { /* 仅优化 */ })
|
|
2758
|
+
}
|
|
2759
|
+
setState({ sidebarUi: 'modern', sidebarRegisterFailed: registered !== true })
|
|
2760
|
+
// 常驻预热:不渲染任何东西,宿主 running 时把 IDE 加载到停放区
|
|
2761
|
+
slots.inject('shell.overlay', () => slots.register(
|
|
2762
|
+
{ name: 'shell.overlay', id: 'code-server', order: 70, label: 'Code Server' },
|
|
2763
|
+
(props) => React.createElement(Resident, props)
|
|
2764
|
+
))
|
|
2765
|
+
// 不注册 conversation.chat.turnTail:官方产物/交付行由 ui-deliverables 渲染,
|
|
2766
|
+
// 我们只通过 patterns/canOpen 认领文件地址,让它的 openFile 落到本 tab。
|
|
2767
|
+
console.log('[code-server] client registered via ' + via + ': sidebar tab=' + registered
|
|
2768
|
+
+ '(claims dsh-resource://file/**) + resident preload + settings card')
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
function onLegacyDsh(reportToHost) {
|
|
2772
|
+
setState({ sidebarUi: 'legacy' })
|
|
2773
|
+
console.warn('[code-server] 未探测到右侧栏服务(sidebarRightTabs/sidebarRight):'
|
|
2774
|
+
+ ' 本插件自 0.2.3 起不再兼容旧版 DSH —— 除「设置 → 插件 → Code Server」的提示外不提供任何入口,'
|
|
2775
|
+
+ '也不预热 IDE。请升级 DSH。')
|
|
2776
|
+
if (reportToHost === true) {
|
|
2777
|
+
// 上报 host:让它别为一个用不了的 UI 自动预启动/继续运行 IDE
|
|
2778
|
+
api('/code-server/ui-mode', { sidebar: false }).catch(function () { /* 只是优化,失败不影响提示 */ })
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
var noticeTimer = null
|
|
2783
|
+
var reportTimer = null
|
|
2784
|
+
var syncTimer = null
|
|
2785
|
+
function clearTimers() {
|
|
2786
|
+
if (noticeTimer !== null) { clearTimeout(noticeTimer); noticeTimer = null }
|
|
2787
|
+
if (reportTimer !== null) { clearTimeout(reportTimer); reportTimer = null }
|
|
2788
|
+
if (syncTimer !== null) { clearTimeout(syncTimer); syncTimer = null }
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
// 统一经 ctx.inject 拿服务上下文(服务已就绪时也会回调,只是可能晚一个 tick)
|
|
2792
|
+
var injectAccepted = false
|
|
2793
|
+
try {
|
|
2794
|
+
if (typeof ctx.inject === 'function') {
|
|
2795
|
+
injectAccepted = true
|
|
2796
|
+
ctx.inject(['sidebarRightTabs', 'sidebarRight'], function (sctx) { onModernUi(sctx, 'inject') })
|
|
2797
|
+
}
|
|
2798
|
+
} catch (e) {
|
|
2799
|
+
console.warn('[code-server] sidebar inject failed:', e != null && e.message != null ? e.message : String(e))
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
// 同步探测:只看"服务是否已在注册表里",用于决定要不要启动旧版判定/兜底注册
|
|
2803
|
+
var syncReady = typeof ctx.get === 'function'
|
|
2804
|
+
&& ctx.get('sidebarRightTabs') !== undefined && ctx.get('sidebarRight') !== undefined
|
|
2805
|
+
|
|
2806
|
+
if (syncReady) {
|
|
2807
|
+
// 服务已在,但 inject 可能迟迟不回调(上下文差异)→ 1.5s 后用同步服务兜底注册,
|
|
2808
|
+
// 避免"卡片正常、侧栏却没有入口"这种静默失败。
|
|
2809
|
+
syncTimer = setTimeout(function () {
|
|
2810
|
+
if (modernSettled) return
|
|
2811
|
+
console.warn('[code-server] ctx.inject 未在 1.5s 内回调,改用同步服务注册侧栏')
|
|
2812
|
+
onModernUi(ctx, 'sync-fallback')
|
|
2813
|
+
}, SYNC_FALLBACK_MS)
|
|
2814
|
+
return
|
|
2815
|
+
}
|
|
2816
|
+
if (injectAccepted !== true) {
|
|
2817
|
+
onLegacyDsh(true)
|
|
2818
|
+
return
|
|
2819
|
+
}
|
|
2820
|
+
// 服务晚到:先只给提示(可逆),更久仍无服务才通知 host 回收/停止预启动
|
|
2821
|
+
noticeTimer = setTimeout(function () {
|
|
2822
|
+
if (modernSettled) return
|
|
2823
|
+
onLegacyDsh(false)
|
|
2824
|
+
}, LEGACY_NOTICE_MS)
|
|
2825
|
+
reportTimer = setTimeout(function () {
|
|
2826
|
+
if (modernSettled) return
|
|
2827
|
+
onLegacyDsh(true)
|
|
2828
|
+
}, LEGACY_REPORT_MS)
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
const inject = ['slots', 'settingsScope']
|
|
2832
|
+
const name = 'code-server'
|
|
2833
|
+
|
|
2834
|
+
// 入口导出(与原 `export { apply, inject, name }` 等价;banner/footer 的 CJS 形态要求)
|
|
2835
|
+
module.exports = { apply: apply, inject: inject, name: name }
|
|
2836
|
+
|
|
2837
|
+
// ---- 测试钩子(唯一为测试而存在的代码;DSH 永不设置该标志)----
|
|
2838
|
+
// scripts/client-bundle-harness.mjs 的 testHooks 选项会先设 window.__dshcsTestHooks = true,
|
|
2839
|
+
// 于是几个"纯函数/纯树"单元套件(workspace 解析、全屏动作、问 DSH 面板)可以直接调这里的内部函数,
|
|
2840
|
+
// 不需要为了可测而把模块拆出去(拆出去就意味着又要有构建)。
|
|
2841
|
+
if (typeof window !== 'undefined' && window.__dshcsTestHooks === true) {
|
|
2842
|
+
module.exports.__internals = {
|
|
2843
|
+
parseFileAddress: parseFileAddress,
|
|
2844
|
+
basenameOfAddress: basenameOfAddress,
|
|
2845
|
+
resolveFilePath: resolveFilePath,
|
|
2846
|
+
isPageAddress: isPageAddress,
|
|
2847
|
+
pickWorkspaceCwd: pickWorkspaceCwd,
|
|
2848
|
+
requestFullscreenPanel: requestFullscreenPanel,
|
|
2849
|
+
getParkStrategy: getParkStrategy,
|
|
2850
|
+
surfaceSnapshot: surfaceSnapshot,
|
|
2851
|
+
normalizeClaimExtensions: normalizeClaimExtensions,
|
|
2852
|
+
parseClaimPolicy: parseClaimPolicy,
|
|
2853
|
+
claimsAddress: claimsAddress,
|
|
2854
|
+
describeClaimPolicy: describeClaimPolicy,
|
|
2855
|
+
DEFAULT_CLAIM_EXTENSIONS: DEFAULT_CLAIM_EXTENSIONS,
|
|
2856
|
+
PREVIEW_FRIENDLY_EXTENSIONS: PREVIEW_FRIENDLY_EXTENSIONS,
|
|
2857
|
+
EXECUTABLE_EXTENSIONS: EXECUTABLE_EXTENSIONS,
|
|
2858
|
+
OFFICE_EXTENSIONS: OFFICE_EXTENSIONS,
|
|
2859
|
+
// 「问 DSH」面板(0.3.59 起手写;面板的每个选择器/每次降级都要能被钉住)
|
|
2860
|
+
ASK_CSS: ASK_CSS,
|
|
2861
|
+
ASK_LABELS: ASK_LABELS,
|
|
2862
|
+
askViewState: askViewState,
|
|
2863
|
+
askApplyPayload: askApplyPayload,
|
|
2864
|
+
askPrimitives: askPrimitives,
|
|
2865
|
+
askComponent: askComponent,
|
|
2866
|
+
askButton: askButton,
|
|
2867
|
+
askSummaryLine: askSummaryLine,
|
|
2868
|
+
askDeliveryNote: askDeliveryNote,
|
|
2869
|
+
askRemainingText: askRemainingText,
|
|
2870
|
+
askErrorView: askErrorView,
|
|
2871
|
+
AskBoundary: AskBoundary,
|
|
2872
|
+
AskDisclosureRow: AskDisclosureRow,
|
|
2873
|
+
AskThreadEntry: AskThreadEntry,
|
|
2874
|
+
AskApprovalCard: AskApprovalCard,
|
|
2875
|
+
AskPanel: AskPanel,
|
|
2876
|
+
AskRoot: AskRoot,
|
|
2877
|
+
askMount: askMount,
|
|
2878
|
+
askPush: askPush,
|
|
2879
|
+
askOnMessage: askOnMessage,
|
|
2880
|
+
askClose: askClose,
|
|
2881
|
+
askEnsureShell: askEnsureShell,
|
|
2882
|
+
askState: ask,
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
return module.exports;}});
|