dsh-vscode-workbench 0.2.4 → 0.2.5
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 +27 -10
- package/README.md +26 -11
- package/cordis.patch.yml +2 -0
- package/lib/client.js +1039 -417
- package/lib/client.js.map +1 -1
- package/lib/index.js +8 -247
- package/lib/index.js.map +1 -1
- package/lib/types/client/DshBrowserPage.d.ts +6 -0
- package/lib/types/client/DshPanelPage.d.ts +9 -0
- package/lib/types/client/Icon.d.ts +1 -1
- package/lib/types/client/TerminalPanel.d.ts +5 -2
- package/lib/types/client/Workbench.d.ts +7 -1
- package/lib/types/client/browser.d.ts +26 -0
- package/lib/types/client/dshPanels.d.ts +4 -0
- package/lib/types/client/shortcuts.d.ts +2 -0
- package/lib/types/client/terminal.d.ts +37 -32
- package/package.json +15 -4
- package/src/client/DshBrowserPage.tsx +61 -0
- package/src/client/DshPanelPage.tsx +63 -0
- package/src/client/Icon.tsx +1 -1
- package/src/client/TerminalPanel.tsx +63 -40
- package/src/client/Workbench.tsx +72 -11
- package/src/client/browser.ts +68 -0
- package/src/client/dshPanels.ts +16 -0
- package/src/client/index.tsx +25 -6
- package/src/client/shortcuts.ts +9 -0
- package/src/client/styles.ts +18 -7
- package/src/client/terminal.ts +125 -78
- package/src/index.ts +0 -2
- package/lib/types/terminal.d.ts +0 -46
- package/src/terminal.ts +0 -173
package/src/client/Workbench.tsx
CHANGED
|
@@ -11,6 +11,10 @@ import type { MarkdownHeading } from './MarkdownPreview.tsx'
|
|
|
11
11
|
import { MonacoDiffEditor, MonacoEditor, isMacPlatform, loadMonaco, preloadMonaco, retainEditorModels } from './MonacoEditor.tsx'
|
|
12
12
|
import { DshConversationPanel, DshConversationTheme } from './DshConversationPanel.tsx'
|
|
13
13
|
import { DshMark } from './DshMark.tsx'
|
|
14
|
+
import { DshPanelPage } from './DshPanelPage.tsx'
|
|
15
|
+
import { DshBrowserPage } from './DshBrowserPage.tsx'
|
|
16
|
+
import type { NativeBrowser, BrowserSnapshot } from './browser.ts'
|
|
17
|
+
import { dshPanelLabel } from './dshPanels.ts'
|
|
14
18
|
import { ActivityBar, normalizeActivityOrder, type ActivityView } from './ActivityBar.tsx'
|
|
15
19
|
import { WorkspaceSwitcher } from './WorkspaceSwitcher.tsx'
|
|
16
20
|
import type { DshNavigation, WorkspaceChoice } from './dshNavigation.ts'
|
|
@@ -438,11 +442,44 @@ const ExplorerView = memo(Explorer)
|
|
|
438
442
|
const SearchView = memo(Search)
|
|
439
443
|
const GitView = memo(Git)
|
|
440
444
|
|
|
441
|
-
export function Workbench({ service, sessionId, cwd, navigation }: { service: VscodeWorkbench; sessionId?: string; cwd?: string; navigation?: DshNavigation }) {
|
|
445
|
+
export function Workbench({ service, browser, sessionId, cwd, navigation, dshPanelId: hostDshPanelId, onOpenDshPanel, onRestoreConversation, onCloseDshPanel }: { service: VscodeWorkbench; browser?: NativeBrowser; sessionId?: string; cwd?: string; navigation?: DshNavigation; dshPanelId?: string; onOpenDshPanel?(panelId: string): void; onRestoreConversation?(): void; onCloseDshPanel?(): void }) {
|
|
442
446
|
const [snapshot, setSnapshot] = useState(() => service.getSnapshot()); const [panels, setPanels] = useState(() => service.getPanels())
|
|
443
447
|
useEffect(() => service.subscribe(() => { setSnapshot(service.getSnapshot()); setPanels(service.getPanels()) }), [service])
|
|
444
|
-
const [workspace,setWorkspace]=useState<{cwd:string;name:string}>(); const [tabs,setTabs]=useState<Tab[]>([]); const [active,setActive]=useState<string>(); const [error,setError]=useState<string>(); const [panelHeight,setPanelHeight]=useState(260); const [sideWidth,setSideWidth]=useState(210); const [primarySideVisible,setPrimarySideVisible]=useState(()=>localStorage.getItem('dvw:primary-side-visible')!=='closed');const [statusVisible,setStatusVisible]=useState(()=>localStorage.getItem('dvw:status-bar-visible')==='open');const [settingsOpen,setSettingsOpen]=useState(false);const [chatWidth,setChatWidth]=useState(()=>clampChatWidth(Number(localStorage.getItem('dvw:dsh-chat-width'))||DSH_CHAT_DEFAULT_WIDTH));const [chatVisible,setChatVisible]=useState(()=>localStorage.getItem('dvw:dsh-chat-visible')!=='closed');const [outlineVisible,setOutlineVisible]=useState(()=>localStorage.getItem('dvw:outline')!=='closed');const [tsIntellisense,setTsIntellisense]=useState(tsIntellisenseEnabled);const [tsIndex,setTsIndex]=useState<{state:'idle'|'loading'|'ready'|'error';files?:number;truncated?:boolean;error?:string}>({state:'idle'});const [lspServers,setLspServers]=useState<LspServerStatus[]>([]);const [lspBusy,setLspBusy]=useState<string>();const [lspError,setLspError]=useState('');const lspManager=useRef<LspManager>();const [fontSize,setFontSize]=useState(()=>{const saved=Math.round(Number(localStorage.getItem('dvw:font-size')));return Number.isFinite(saved)&&saved>=11&&saved<=20?saved:14});const [tabMenu,setTabMenu]=useState<{x:number;y:number;path:string}>()
|
|
448
|
+
const [workspace,setWorkspace]=useState<{cwd:string;name:string}>(); const [tabs,setTabs]=useState<Tab[]>([]); const [active,setActive]=useState<string>(); const [dshTabActive,setDshTabActive]=useState(hostDshPanelId!==undefined); const [dshTabId,setDshTabId]=useState<string|undefined>(hostDshPanelId); const dshPanelId=dshTabId; const [error,setError]=useState<string>(); const [panelHeight,setPanelHeight]=useState(260); const [sideWidth,setSideWidth]=useState(210); const [primarySideVisible,setPrimarySideVisible]=useState(()=>localStorage.getItem('dvw:primary-side-visible')!=='closed');const [statusVisible,setStatusVisible]=useState(()=>localStorage.getItem('dvw:status-bar-visible')==='open');const [settingsOpen,setSettingsOpen]=useState(false);const [chatWidth,setChatWidth]=useState(()=>clampChatWidth(Number(localStorage.getItem('dvw:dsh-chat-width'))||DSH_CHAT_DEFAULT_WIDTH));const [chatVisible,setChatVisible]=useState(()=>localStorage.getItem('dvw:dsh-chat-visible')!=='closed');const [outlineVisible,setOutlineVisible]=useState(()=>localStorage.getItem('dvw:outline')!=='closed');const [tsIntellisense,setTsIntellisense]=useState(tsIntellisenseEnabled);const [tsIndex,setTsIndex]=useState<{state:'idle'|'loading'|'ready'|'error';files?:number;truncated?:boolean;error?:string}>({state:'idle'});const [lspServers,setLspServers]=useState<LspServerStatus[]>([]);const [lspBusy,setLspBusy]=useState<string>();const [lspError,setLspError]=useState('');const lspManager=useRef<LspManager>();const [fontSize,setFontSize]=useState(()=>{const saved=Math.round(Number(localStorage.getItem('dvw:font-size')));return Number.isFinite(saved)&&saved>=11&&saved<=20?saved:14});const [tabMenu,setTabMenu]=useState<{x:number;y:number;path:string}>()
|
|
449
|
+
const [browserSnapshot,setBrowserSnapshot]=useState<BrowserSnapshot>(()=>browser?.getSnapshot()??{available:false,tabs:[]})
|
|
450
|
+
const [browserId,setBrowserId]=useState<string>()
|
|
451
|
+
const browserTabs=browserSnapshot.tabs.filter(tab=>tab.sessionId===sessionId)
|
|
452
|
+
const previousBrowserIds=useRef(new Set(browserTabs.map(tab=>tab.id)))
|
|
453
|
+
useEffect(()=>{if(browser===undefined)return;setBrowserSnapshot(browser.getSnapshot());return browser.subscribe(()=>setBrowserSnapshot(browser.getSnapshot()))},[browser])
|
|
454
|
+
useEffect(()=>{
|
|
455
|
+
const next=new Set(browserTabs.map(tab=>tab.id))
|
|
456
|
+
const added=browserTabs.find(tab=>!previousBrowserIds.current.has(tab.id))
|
|
457
|
+
previousBrowserIds.current=next
|
|
458
|
+
if(added!==undefined&&snapshot.visible){setBrowserId(added.id);setDshTabActive(false)}
|
|
459
|
+
else if(browserId!==undefined&&!next.has(browserId))setBrowserId(undefined)
|
|
460
|
+
},[browserSnapshot,sessionId,browserId,snapshot.visible])
|
|
461
|
+
const browserRequest=useRef<AbortController>()
|
|
462
|
+
useEffect(()=>()=>browserRequest.current?.abort(),[browser,sessionId,snapshot.visible])
|
|
463
|
+
const newBrowser=async()=>{
|
|
464
|
+
if(browser===undefined||sessionId===undefined)return
|
|
465
|
+
setDshTabActive(false);onRestoreConversation?.()
|
|
466
|
+
browserRequest.current?.abort();const request=new AbortController();browserRequest.current=request
|
|
467
|
+
try{setBrowserId(await browser.open(sessionId,request.signal))}catch(error){if(!request.signal.aborted)setError(error instanceof Error?error.message:String(error))}
|
|
468
|
+
}
|
|
469
|
+
useEffect(()=>{if(browserId!==undefined&&!dshTabActive)browser?.focus(browserId)},[browser,browserId,dshTabActive])
|
|
470
|
+
useEffect(()=>{if(!snapshot.visible)return;const open=()=>void newBrowser();window.addEventListener('dvw-browser-new',open);return()=>window.removeEventListener('dvw-browser-new',open)},[browser,sessionId,snapshot.visible])
|
|
471
|
+
useEffect(()=>{if(hostDshPanelId!==undefined)setBrowserId(undefined)},[hostDshPanelId])
|
|
445
472
|
const tabsRef=useRef(tabs);tabsRef.current=tabs
|
|
473
|
+
// DSH unmounts its conversation while a global panel (Plugins…) is selected,
|
|
474
|
+
// so the tab outlives the panel: opening a file hands the centre back to the
|
|
475
|
+
// conversation, and clicking the tab re-selects the panel.
|
|
476
|
+
const dshTabActiveRef=useRef(dshTabActive);dshTabActiveRef.current=dshTabActive
|
|
477
|
+
const dshTabIdRef=useRef(dshTabId);dshTabIdRef.current=dshTabId
|
|
478
|
+
const hostDshPanelIdRef=useRef(hostDshPanelId);hostDshPanelIdRef.current=hostDshPanelId
|
|
479
|
+
const openDshPanelRef=useRef(onOpenDshPanel);openDshPanelRef.current=onOpenDshPanel
|
|
480
|
+
const restoreConversationRef=useRef(onRestoreConversation);restoreConversationRef.current=onRestoreConversation
|
|
481
|
+
const leaveDshPanel=useCallback(()=>{setBrowserId(undefined);const wasActive=dshTabActiveRef.current;setDshTabActive(false);if(wasActive)restoreConversationRef.current?.()},[])
|
|
482
|
+
const openDshTab=useCallback(()=>{setBrowserId(undefined);const id=dshTabIdRef.current;if(id===undefined)return;setDshTabActive(true);if(hostDshPanelIdRef.current!==id)openDshPanelRef.current?.(id)},[])
|
|
446
483
|
const [settingsSection,setSettingsSection]=useState<'appearance'|'layout'|'commit'|'language'>('appearance')
|
|
447
484
|
const tabListRef=useRef<HTMLDivElement>(null)
|
|
448
485
|
const workspaceRequests=useRef(new WorkspaceRequestScope());const loadedWorkspaceCwd=useRef<string>();const loadingTabs=useRef(new Map<string, symbol>())
|
|
@@ -464,6 +501,23 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
|
|
|
464
501
|
useEffect(()=>localStorage.setItem('dvw:primary-side-visible',primarySideVisible?'open':'closed'),[primarySideVisible])
|
|
465
502
|
useEffect(()=>localStorage.setItem('dvw:status-bar-visible',statusVisible?'open':'closed'),[statusVisible])
|
|
466
503
|
useEffect(()=>localStorage.setItem('dvw:font-size',String(fontSize)),[fontSize])
|
|
504
|
+
useEffect(()=>{
|
|
505
|
+
if(hostDshPanelId!==undefined){setDshTabId(hostDshPanelId);setDshTabActive(true);return}
|
|
506
|
+
// The panel went away while it was on screen (closed outside the tab), so forget the tab.
|
|
507
|
+
if(dshTabActiveRef.current)setDshTabId(undefined)
|
|
508
|
+
},[hostDshPanelId])
|
|
509
|
+
// The host only mounts its conversation while no global panel is selected, so
|
|
510
|
+
// showing a file must hand the centre back; clicking the tab re-selects it.
|
|
511
|
+
useEffect(()=>{
|
|
512
|
+
if(dshTabActive||dshTabId===undefined||hostDshPanelId===undefined)return
|
|
513
|
+
restoreConversationRef.current?.()
|
|
514
|
+
},[dshTabActive,dshTabId,hostDshPanelId])
|
|
515
|
+
useEffect(()=>{
|
|
516
|
+
if(!dshTabActive)return
|
|
517
|
+
const id=dshTabIdRef.current
|
|
518
|
+
if(id===undefined||hostDshPanelId===id)return
|
|
519
|
+
openDshPanelRef.current?.(id)
|
|
520
|
+
},[dshTabActive,hostDshPanelId])
|
|
467
521
|
useEffect(()=>{if(tabMenu===undefined)return;const close=()=>setTabMenu(undefined);const escape=(event:KeyboardEvent)=>{if(event.key==='Escape')close()};window.addEventListener('pointerdown',close);window.addEventListener('keydown',escape);return()=>{window.removeEventListener('pointerdown',close);window.removeEventListener('keydown',escape)}},[tabMenu])
|
|
468
522
|
useEffect(() => {
|
|
469
523
|
if (!snapshot.visible) return
|
|
@@ -474,7 +528,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
|
|
|
474
528
|
// The conversation may still be attaching after the chat column opens, so retry for about a second.
|
|
475
529
|
const addToDsh=useCallback((reference:string)=>{setChatVisible(true);let frames=0;const attempt=()=>{void insertIntoDshComposer(reference).then(done=>{if(done)return;if(++frames<60){requestAnimationFrame(attempt);return}alert('DSH 对话输入框当前不可用,请先选择会话或等待当前回复结束。')})};requestAnimationFrame(attempt)},[])
|
|
476
530
|
const addEntryToDsh=useCallback((path:string,directory:boolean)=>addToDsh(dshReference(path,{directory})),[addToDsh])
|
|
477
|
-
const openForWorkspace=(path:string,line?:number,column?:number,highlight?:string)=>{const existing=tabsRef.current.find(tab=>tab.path===path);if(existing)setTabs(rows=>rows.map(tab=>tab.path===path?{...tab,line,column,highlight,preview:line!==undefined?false:tab.preview}:tab));else setTabs(rows=>[...rows,{...unloadedTab(path),line,column,highlight,...(line!==undefined?{preview:false}:{})}]);setActive(path)}
|
|
531
|
+
const openForWorkspace=(path:string,line?:number,column?:number,highlight?:string)=>{const existing=tabsRef.current.find(tab=>tab.path===path);if(existing)setTabs(rows=>rows.map(tab=>tab.path===path?{...tab,line,column,highlight,preview:line!==undefined?false:tab.preview}:tab));else setTabs(rows=>[...rows,{...unloadedTab(path),line,column,highlight,...(line!==undefined?{preview:false}:{})}]);leaveDshPanel();setActive(path)}
|
|
478
532
|
useEffect(() => {
|
|
479
533
|
if (sessionId === undefined) return
|
|
480
534
|
// Switching sessions inside one workspace must keep the explorer, tabs and layout as they are.
|
|
@@ -671,7 +725,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
|
|
|
671
725
|
return()=>{cancelled=true;for(const disposable of disposables)disposable.dispose()}
|
|
672
726
|
},[snapshot.visible,sessionId,workspace,tsIntellisense])
|
|
673
727
|
const openDiff=useCallback((path:string,diff:GitDiff,title?:string,diffSourcePath?:string)=>{const key=`diff:${path}`;const source=diffSourcePath??path;// Previewable documents open like VS Code's PDF/Office editors; history diffs keep the binary notice.
|
|
674
|
-
if(isPreviewDocumentPath(source)&&!path.startsWith('commit:')){openPath(source);return}const binary=isBinaryDiffPath(source);setTabs(rows=>[...rows.filter(row=>row.path!==key),{path:key,title,content:diff.diff,draft:diff.diff,revision:'',size:diff.diff.length,binary,tooLarge:false,dirty:false,diff:binary?undefined:diff,diffMode:'inline',diffSourcePath:source}]);setActive(key)},[openPath])
|
|
728
|
+
if(isPreviewDocumentPath(source)&&!path.startsWith('commit:')){openPath(source);return}const binary=isBinaryDiffPath(source);setTabs(rows=>[...rows.filter(row=>row.path!==key),{path:key,title,content:diff.diff,draft:diff.diff,revision:'',size:diff.diff.length,binary,tooLarge:false,dirty:false,diff:binary?undefined:diff,diffMode:'inline',diffSourcePath:source}]);leaveDshPanel();setActive(key)},[openPath,leaveDshPanel])
|
|
675
729
|
useEffect(()=>{const listener=(event:Event)=>void open((event as CustomEvent<string>).detail);window.addEventListener('dvw-open-workspace-path',listener);return()=>window.removeEventListener('dvw-open-workspace-path',listener)},[sessionId,tabs])
|
|
676
730
|
const savingTabs = useRef(new Set<string>())
|
|
677
731
|
const save = async (tab: Tab) => {
|
|
@@ -693,7 +747,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
|
|
|
693
747
|
const activeTab=tabs.find(tab=>tab.path===active); const Panel=panels.find(panel=>panel.id===snapshot.bottomPanel)?.component; const terminalAvailable=panels.some(panel=>panel.id==='terminal')
|
|
694
748
|
const menuTab=tabMenu===undefined?undefined:tabs.find(tab=>tab.path===tabMenu.path); const menuIndex=menuTab===undefined?-1:tabs.findIndex(tab=>tab.path===menuTab.path); const savedTabs=tabs.filter(tab=>!tab.dirty)
|
|
695
749
|
const selectView=(view:WorkbenchView)=>{if(snapshot.view===view&&primarySideVisible){setPrimarySideVisible(false);return}setPrimarySideVisible(true);service.show(view)}
|
|
696
|
-
const ACTIVITY_IDS=['explorer','search','git','terminal','chat'] as const
|
|
750
|
+
const ACTIVITY_IDS=['explorer','search','git','terminal','browser','chat'] as const
|
|
697
751
|
const [activityOrder,setActivityOrder]=useState<string[]>(()=>normalizeActivityOrder(readActivityJSON('dvw:activity-order'),ACTIVITY_IDS))
|
|
698
752
|
const [activityHidden,setActivityHidden]=useState<string[]>(()=>{const value=readActivityJSON('dvw:activity-hidden');return Array.isArray(value)?value.filter((id):id is string=>typeof id==='string'&&(ACTIVITY_IDS as readonly string[]).includes(id)):[]})
|
|
699
753
|
useEffect(()=>localStorage.setItem('dvw:activity-order',JSON.stringify(activityOrder)),[activityOrder])
|
|
@@ -707,15 +761,20 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
|
|
|
707
761
|
const suppressAttachmentDrop=(event:DragEvent)=>{if(!event.dataTransfer.types.includes('Files'))return;const target=event.target;const insideTree=target instanceof Element&&target.closest('.dvw-tree')!==null;if(!insideTree){event.preventDefault();event.stopPropagation()}}
|
|
708
762
|
if(!snapshot.visible)return null
|
|
709
763
|
if(sessionId===undefined)return <div className="dvw-overlay" data-dvw-theme={colorScheme} data-dvw-theme-name={theme}><WorkbenchTitlebar/><div className="dvw-empty" style={{gridColumn:'1/4'}}><div>请先选择或创建一个 DSH 会话。<br/><button onClick={()=>service.hide()}>返回 DSH</button></div></div></div>
|
|
764
|
+
const showingDshPanel=dshTabId!==undefined&&dshTabActive&&!settingsOpen
|
|
765
|
+
const closeDshPanel=()=>{setDshTabActive(false);setDshTabId(undefined);onCloseDshPanel?.()}
|
|
766
|
+
const openFromView=(path:string,line?:number,column?:number,highlight?:string)=>{leaveDshPanel();openPath(path,line,column,highlight)}
|
|
767
|
+
const chatShown=chatVisible&&!settingsOpen&&!showingDshPanel
|
|
710
768
|
const markdown=activeTab!==undefined&&!activeTab.path.startsWith('diff:')&&/\.md$/i.test(activeTab.path)
|
|
711
769
|
const diffMarkdown=activeTab?.diff!==undefined&&/\.md$/i.test(activeTab.diffSourcePath??'')
|
|
712
770
|
const markdownPreview=markdown||(diffMarkdown&&activeTab?.preview===true)
|
|
713
771
|
const headings=markdownPreview&&activeTab?markdownHeadings(activeTab.diff?.modified??activeTab.draft):[]
|
|
714
|
-
return <div className="dvw-overlay" data-dvw-theme={colorScheme} data-dvw-theme-name={theme} data-dvw-chat={
|
|
772
|
+
return <div className="dvw-overlay" data-dvw-theme={colorScheme} data-dvw-theme-name={theme} data-dvw-chat={chatShown||undefined} data-dvw-primary-side={primarySideVisible||undefined} data-dvw-status={statusVisible||undefined} data-dvw-bottom-panel={snapshot.bottomPanel||undefined} style={{'--dvw-side-width':`${sideWidth}px`,'--dvw-chat-width':`${chatWidth}px`} as CSSProperties} role="dialog" aria-label="VS Code 工作台" onDragEnterCapture={suppressAttachmentDrop} onDragOverCapture={suppressAttachmentDrop} onDropCapture={suppressAttachmentDrop}>
|
|
715
773
|
<WorkbenchTitlebar name={workspace?.name}/>
|
|
716
|
-
|
|
717
|
-
<
|
|
718
|
-
|
|
774
|
+
{dshTabId!==undefined&&<DshPanelPage panelId={dshTabId} active={showingDshPanel}/>}
|
|
775
|
+
<DshConversationPanel visible={chatShown} width={chatWidth} statusHeight={statusVisible?30:6}/>
|
|
776
|
+
<DshConversationTheme visible={chatShown} colorScheme={colorScheme} followDsh={false}/>
|
|
777
|
+
{chatShown&&<aside className="dvw-chat"><PanelResizer className="dvw-chat-resizer" label="拖动调整 DSH 对话栏宽度" width={chatWidth} min={DSH_CHAT_MIN_WIDTH} max={DSH_CHAT_MAX_WIDTH} defaultWidth={DSH_CHAT_DEFAULT_WIDTH} direction={-1} hideBelow={200} onHide={()=>setChatVisible(false)} onWidth={setChatWidth}/><header className="dvw-chat-head"><span><DshMark/> DSH</span><button className="dvw-icon" title="关闭 DSH 对话栏" onClick={()=>setChatVisible(false)}><Icon name="close"/></button></header><div className="dvw-chat-body"/></aside>}
|
|
719
778
|
{switcherOpen&&<WorkspaceSwitcher choices={switcherChoices} activeSessionId={sessionId} unavailable={navigation?.available===false} busy={switcherBusy} error={switcherError} theme={colorScheme} onCreate={()=>void createWorkspace()} onSwitch={switchSession} onOpenWorkspace={enterWorkspace} onClose={()=>setSwitcherOpen(false)}/>}
|
|
720
779
|
{settingsOpen&&<div className="dvw-settings-backdrop" role="presentation" onMouseDown={()=>setSettingsOpen(false)}>
|
|
721
780
|
<section className="dvw-settings" role="dialog" aria-modal="true" aria-label="工作台设置" onMouseDown={event=>event.stopPropagation()}>
|
|
@@ -752,6 +811,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
|
|
|
752
811
|
{id:'search',label:'搜索',icon:<Icon name="search"/>,active:primarySideVisible&&snapshot.view==='search',onActivate:()=>selectView('search')},
|
|
753
812
|
{id:'git',label:'源代码管理',icon:<Icon name="source-control"/>,active:primarySideVisible&&snapshot.view==='git',onActivate:()=>selectView('git')},
|
|
754
813
|
{id:'terminal',label:terminalAvailable?(snapshot.bottomPanel==='terminal'?'隐藏终端':'显示终端'):'终端适配器未安装',icon:<Icon name="terminal"/>,active:snapshot.bottomPanel==='terminal',disabled:!terminalAvailable,onActivate:()=>toggleTerminal(service)},
|
|
814
|
+
{id:'browser',label:'新建浏览器',icon:<Icon name="globe"/>,active:browserId!==undefined,disabled:!browserSnapshot.available||sessionId===undefined,onActivate:()=>void newBrowser()},
|
|
755
815
|
{id:'chat',label:chatVisible?'关闭 DSH 对话栏 (Ctrl+Alt+B)':'显示 DSH 对话栏 (Ctrl+Alt+B)',icon:<DshMark size={22}/>,active:chatVisible,onActivate:()=>setChatVisible(value=>!value)},
|
|
756
816
|
]}
|
|
757
817
|
fixed={[{id:'workspaces',label:'打开工作区 / 切换会话',icon:<Icon name="folder-opened"/>,active:false,onActivate:openSwitcher}]}
|
|
@@ -761,9 +821,9 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
|
|
|
761
821
|
]}
|
|
762
822
|
order={activityOrder} hidden={activityHidden}
|
|
763
823
|
onOrderChange={setActivityOrder} onHiddenChange={setActivityHidden}/>
|
|
764
|
-
<aside className="dvw-side" data-side-width={sideWidth}>{snapshot.view==='explorer'&&workspace!==undefined&&<ExplorerView sessionId={sessionId} cwd={workspace.cwd} selected={active} onOpen={
|
|
824
|
+
<aside className="dvw-side" data-side-width={sideWidth}>{snapshot.view==='explorer'&&workspace!==undefined&&<ExplorerView sessionId={sessionId} cwd={workspace.cwd} selected={active} onOpen={openFromView} onAddToDsh={addEntryToDsh}/>} {snapshot.view==='search'&&<SearchView sessionId={sessionId} onOpen={openFromView}/>} {snapshot.view==='git'&&<GitView sessionId={sessionId} preferences={commitPreferences} onDiff={(path,diff,title,source)=>{leaveDshPanel();openDiff(path,diff,title,source)}}/>}</aside>
|
|
765
825
|
<PanelResizer className="dvw-side-resizer" label="拖动调整源码管理侧边栏宽度" width={sideWidth} min={210} max={640} defaultWidth={210} direction={1} hideBelow={105} onHide={()=>setPrimarySideVisible(false)} onWidth={setSideWidth}/>
|
|
766
|
-
<main className="dvw-main" onDragOver={event => { if (draggedEntry?.kind !== 'file') return; event.preventDefault(); event.dataTransfer.dropEffect = 'copy' }} onDrop={event => { const entry = draggedEntry; if (entry?.kind !== 'file') return; event.preventDefault(); endEntryDrag(); openPath(entry.path) }}><div className="dvw-tabs" data-empty={tabs.length===0||undefined}><div className="dvw-tab-list" ref={tabListRef}>{tabs.map(tab=><button className="dvw-tab" data-active={tab.path===active} key={tab.path} title={tab.diffSourcePath??tab.path} onClick={()=>setActive(tab.path)} onAuxClick={event=>{if(event.button===1){event.preventDefault();close(tab)}}} onMouseDown={event=>{if(event.button===1)event.preventDefault()}} onContextMenu={event=>{event.preventDefault();setActive(tab.path);setTabMenu({x:event.clientX,y:event.clientY,path:tab.path})}}><FileIcon name={tabLabel(tab)} className="dvw-tab-icon"/><span className="dvw-tab-name">{tab.dirty?'● ':''}{tabLabel(tab)}</span><span className="dvw-tab-close" onClick={event=>{event.stopPropagation();close(tab)}}><Icon name="close"/></span></button>)}</div></div><div className="dvw-editor" data-outline={markdownPreview&&outlineVisible&&headings.length>0}>{error&&<div className="dvw-error"><button className="dvw-icon" onClick={()=>setError(undefined)}><Icon name="close"/></button>{error}</div>}{markdownPreview&&outlineVisible&&headings.length>0&&<MarkdownOutline headings={headings} onClose={()=>setOutlineVisible(false)} onHeading={selectHeading}/>} {markdown&&activeTab&&<div className="dvw-editor-toolbar"><button className="dvw-icon dvw-outline-toggle" data-active={outlineVisible} title={outlineVisible?'关闭 Markdown 大纲':'显示 Markdown 大纲'} onClick={()=>setOutlineVisible(value=>!value)}><Icon name="list-tree"/></button><button className="dvw-mode-button" data-active={!activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:false}:tab))}><Icon name="code"/> 源码</button><button className="dvw-mode-button" data-active={activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:true}:tab))}><Icon name="preview"/> 预览</button></div>}{activeTab?.diff&&<div className="dvw-editor-toolbar dvw-diff-toolbar">{diffMarkdown&&<div className="dvw-diff-preview-switch"><button className="dvw-mode-button" data-diff-view="diff" data-active={!activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:false}:tab))}><Icon name="diff"/> 差异</button><button className="dvw-mode-button" data-diff-view="preview" data-active={activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:true}:tab))}><Icon name="preview"/> 预览</button></div>}{!activeTab.preview&&<><button className="dvw-mode-button dvw-diff-mode-button" data-mode="inline" data-active={activeTab.diffMode!=='split'} title="内联视图" onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,diffMode:'inline'}:tab))}><Icon name="diff-single"/> 内联</button><button className="dvw-mode-button dvw-diff-mode-button" data-mode="split" data-active={activeTab.diffMode==='split'} title="并排视图" onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,diffMode:'split'}:tab))}><Icon name="diff-sidebyside"/> 并排</button></>}</div>}<div className="dvw-editor-body" data-toolbar={markdown||activeTab?.diff!==undefined}>{activeTab===undefined?<div className="dvw-empty"><div><strong>DSH Workbench</strong><br
|
|
826
|
+
<main className="dvw-main" onDragOver={event => { if (draggedEntry?.kind !== 'file') return; event.preventDefault(); event.dataTransfer.dropEffect = 'copy' }} onDrop={event => { const entry = draggedEntry; if (entry?.kind !== 'file') return; event.preventDefault(); endEntryDrag(); openPath(entry.path) }}><div className="dvw-tabs" data-empty={(tabs.length===0&&dshPanelId===undefined&&browserTabs.length===0)||undefined}><div className="dvw-tab-list" ref={tabListRef}>{browserTabs.map(tab=><button className="dvw-tab" key={tab.id} data-dvw-browser-tab={tab.id} data-active={browserId===tab.id&&!dshTabActive} title={tab.title} onClick={()=>{leaveDshPanel();setBrowserId(tab.id)}}><Icon name="globe"/><span className="dvw-tab-name">{tab.title}</span><span className="dvw-tab-close" onClick={event=>{event.stopPropagation();browser?.close(tab.sessionId,tab.id)}}><Icon name="close"/></span></button>)}{dshPanelId!==undefined&&<button className="dvw-tab" data-active={dshTabActive} data-dvw-dsh-tab="" title={`${dshPanelLabel(dshPanelId)}(DSH 官方页面)`} onClick={()=>setDshTabActive(true)}><span className="dvw-dsh-tab-mark"/><span className="dvw-tab-name">{dshPanelLabel(dshPanelId)}</span><span className="dvw-tab-close" onClick={event=>{event.stopPropagation();closeDshPanel()}}><Icon name="close"/></span></button>}{tabs.map(tab=><button className="dvw-tab" data-active={tab.path===active&&!dshTabActive&&browserId===undefined} key={tab.path} title={tab.diffSourcePath??tab.path} onClick={()=>{setBrowserId(undefined);setDshTabActive(false);setActive(tab.path)}} onAuxClick={event=>{if(event.button===1){event.preventDefault();close(tab)}}} onMouseDown={event=>{if(event.button===1)event.preventDefault()}} onContextMenu={event=>{event.preventDefault();setBrowserId(undefined);setDshTabActive(false);setActive(tab.path);setTabMenu({x:event.clientX,y:event.clientY,path:tab.path})}}><FileIcon name={tabLabel(tab)} className="dvw-tab-icon"/><span className="dvw-tab-name">{tab.dirty?'● ':''}{tabLabel(tab)}</span><span className="dvw-tab-close" onClick={event=>{event.stopPropagation();close(tab)}}><Icon name="close"/></span></button>)}</div></div><div className="dvw-editor" data-outline={markdownPreview&&outlineVisible&&headings.length>0} data-browser-active={browserId!==undefined||undefined}>{error&&<div className="dvw-error"><button className="dvw-icon" onClick={()=>setError(undefined)}><Icon name="close"/></button>{error}</div>}{markdownPreview&&outlineVisible&&headings.length>0&&<MarkdownOutline headings={headings} onClose={()=>setOutlineVisible(false)} onHeading={selectHeading}/>} {markdown&&activeTab&&<div className="dvw-editor-toolbar"><button className="dvw-icon dvw-outline-toggle" data-active={outlineVisible} title={outlineVisible?'关闭 Markdown 大纲':'显示 Markdown 大纲'} onClick={()=>setOutlineVisible(value=>!value)}><Icon name="list-tree"/></button><button className="dvw-mode-button" data-active={!activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:false}:tab))}><Icon name="code"/> 源码</button><button className="dvw-mode-button" data-active={activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:true}:tab))}><Icon name="preview"/> 预览</button></div>}{activeTab?.diff&&<div className="dvw-editor-toolbar dvw-diff-toolbar">{diffMarkdown&&<div className="dvw-diff-preview-switch"><button className="dvw-mode-button" data-diff-view="diff" data-active={!activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:false}:tab))}><Icon name="diff"/> 差异</button><button className="dvw-mode-button" data-diff-view="preview" data-active={activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:true}:tab))}><Icon name="preview"/> 预览</button></div>}{!activeTab.preview&&<><button className="dvw-mode-button dvw-diff-mode-button" data-mode="inline" data-active={activeTab.diffMode!=='split'} title="内联视图" onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,diffMode:'inline'}:tab))}><Icon name="diff-single"/> 内联</button><button className="dvw-mode-button dvw-diff-mode-button" data-mode="split" data-active={activeTab.diffMode==='split'} title="并排视图" onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,diffMode:'split'}:tab))}><Icon name="diff-sidebyside"/> 并排</button></>}</div>}<div className="dvw-editor-body" data-toolbar={markdown||activeTab?.diff!==undefined}>{activeTab===undefined?<div className="dvw-empty"><div><strong>DSH Workbench</strong><br/>从资源管理器或搜索结果打开文件<div className="dvw-start-tools"><button onClick={()=>{if(snapshot.bottomPanel!=='terminal')service.toggleBottomPanel('terminal');queueMicrotask(()=>service.runCommand('terminal.new'))}} disabled={!terminalAvailable}><Icon name="terminal"/>新建终端</button>{browser!==undefined&&<button onClick={()=>void newBrowser()} disabled={!browserSnapshot.available}><Icon name="globe"/>浏览器</button>}</div></div></div>:activeTab.diff?diffMarkdown&&activeTab.preview?<MarkdownPreview source={activeTab.diff.modified} sessionId={sessionId} path={activeTab.diffSourcePath??''}/>:<MonacoDiffEditor uri={`${workspace?.cwd??''}/${activeTab.path}`} original={activeTab.diff.original} modified={activeTab.diff.modified} mode={activeTab.diffMode??'inline'} theme={colorScheme} fontSize={fontSize}/>:activeTab.imageUrl?<div className="dvw-image-preview"><img src={activeTab.imageUrl} alt={activeTab.path}/><span>{activeTab.path}</span></div>:activeTab.documentPreview&&activeTab.documentUrl?<DocumentPreview kind={activeTab.documentPreview} src={activeTab.documentUrl} path={activeTab.path}/>:markdown&&activeTab.preview?<MarkdownPreview source={activeTab.draft} sessionId={sessionId} path={activeTab.path}/>:activeTab.binary||activeTab.tooLarge?<div className="dvw-message">{activeTab.binary?'二进制文件无法在文本编辑器或差异视图中打开':`文件超过 5 MiB:${activeTab.path}`}{activeTab.binary&&activeTab.diffSourcePath!==undefined&&!activeTab.path.startsWith('diff:commit:')&&<button onClick={()=>void openPath(activeTab.diffSourcePath!)}>打开文件</button>}</div>:activeTab.loaded===false?<div className="dvw-message">正在加载文件…</div>:<MonacoEditor uri={`${workspace?.cwd??''}/${activeTab.path}`} onAddToDsh={selection=>addToDsh(dshReference(activeTab.path,{selection}))} value={activeTab.draft} theme={colorScheme} fontSize={fontSize} line={activeTab.line} column={activeTab.column} highlight={activeTab.highlight} onChange={draft=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,draft,dirty:draft!==tab.content}:tab))} onSave={()=>void save(activeTab)}/>}</div></div>{snapshot.bottomPanel===undefined&&<div className="dvw-bottom-reveal" title="向上拖动打开终端" onPointerDown={event=>{if(event.button!==0)return;event.preventDefault();service.toggleBottomPanel('terminal');const statusHeight=statusVisible?24:0;const heightAt=(clientY:number)=>Math.max(120,Math.min(window.innerHeight-160,window.innerHeight-clientY-statusHeight));const move=(e:PointerEvent)=>setPanelHeight(heightAt(e.clientY));const up=()=>{window.removeEventListener('pointermove',move);window.removeEventListener('pointerup',up)};window.addEventListener('pointermove',move);window.addEventListener('pointerup',up)}}/>}</main>
|
|
767
827
|
{tabMenu&&menuTab&&<div className="dvw-context-menu dvw-tab-menu" role="menu" style={{left:Math.min(tabMenu.x,window.innerWidth-240),top:Math.min(tabMenu.y,window.innerHeight-180)}} onPointerDown={event=>event.stopPropagation()}>
|
|
768
828
|
<button role="menuitem" onClick={()=>{setTabMenu(undefined);close(menuTab)}}>关闭<span>Ctrl+F4</span></button>
|
|
769
829
|
<button role="menuitem" disabled={tabs.length<=1} onClick={()=>removeTabs(tabs.filter(row=>row.path!==menuTab.path),'关闭其他标签页')}>关闭其他</button>
|
|
@@ -771,6 +831,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
|
|
|
771
831
|
<button role="menuitem" disabled={savedTabs.length===0} onClick={()=>removeTabs(savedTabs,'关闭已保存标签页')}>关闭已保存<span>Ctrl+K U</span></button>
|
|
772
832
|
<button role="menuitem" onClick={closeAll}>全部关闭<span>Ctrl+K W</span></button>
|
|
773
833
|
</div>}
|
|
834
|
+
{sessionId!==undefined&&browserTabs.map(tab=><DshBrowserPage key={tab.id} sessionId={sessionId} tabId={tab.id} colorScheme={colorScheme} active={browserId===tab.id&&!dshTabActive&&!settingsOpen&&!switcherOpen&&snapshot.visible}/>)}
|
|
774
835
|
{snapshot.bottomPanel&&<section className="dvw-bottom" style={{height:panelHeight}}><div className="dvw-bottom-resizer" onPointerDown={event=>{if(event.button!==0)return;event.preventDefault();const startY=event.clientY,startHeight=panelHeight;const move=(e:PointerEvent)=>setPanelHeight(Math.max(120,Math.min(window.innerHeight-160,startHeight+startY-e.clientY)));const up=(e:PointerEvent)=>{window.removeEventListener('pointermove',move);window.removeEventListener('pointerup',up);if(startHeight+startY-e.clientY<80)service.toggleBottomPanel(snapshot.bottomPanel!)};window.addEventListener('pointermove',move);window.addEventListener('pointerup',up)}}/><div className="dvw-bottom-head"><button className="dvw-bottom-title">{panels.find(p=>p.id===snapshot.bottomPanel)?.title??snapshot.bottomPanel}</button>{snapshot.bottomPanel==='terminal'&&<button className="dvw-icon" title="新建终端" onClick={()=>window.dispatchEvent(new Event('dvw-terminal-new'))}><Icon name="add"/></button>}<button className="dvw-icon" style={{marginLeft:'auto'}} onClick={()=>service.toggleBottomPanel(snapshot.bottomPanel!)}><Icon name="close"/></button></div><div className="dvw-bottom-body">{Panel&&workspace&&<Panel sessionId={sessionId} cwd={workspace.cwd} visible colorScheme={colorScheme}/>}</div></section>}
|
|
775
836
|
<footer className="dvw-status"><span><Icon name="git-branch"/> 工作区</span><span>{workspace?.cwd??cwd??''}</span>{activeTab?.external&&<span>磁盘文件已变化</span>}<button className="dvw-icon" style={{color:'inherit',marginLeft:'auto'}} onClick={()=>service.hide()}>返回 DSH</button></footer>
|
|
776
837
|
</div>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
2
|
+
import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
|
|
3
|
+
import type { TabId } from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
|
|
4
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
|
5
|
+
|
|
6
|
+
export interface BrowserTab { id: string; sessionId: string; title: string }
|
|
7
|
+
export interface BrowserSnapshot { available: boolean; tabs: readonly BrowserTab[] }
|
|
8
|
+
|
|
9
|
+
/** Navigate official browser tabs; their controller, renderer and persistence stay with DSH. */
|
|
10
|
+
export class NativeBrowser {
|
|
11
|
+
private snapshot: BrowserSnapshot = { available: false, tabs: [] }
|
|
12
|
+
private readonly listeners = new Set<() => void>()
|
|
13
|
+
private readonly lifetime = new AbortController()
|
|
14
|
+
private readonly disposers: Array<() => void>
|
|
15
|
+
|
|
16
|
+
constructor(private readonly ctx: Context) {
|
|
17
|
+
this.disposers = [ctx.sidebarRight.openTabs.subscribe(() => this.update()), ctx.sidebarRightTabs.subscribe(() => this.update())]
|
|
18
|
+
this.update()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getSnapshot = (): BrowserSnapshot => this.snapshot
|
|
22
|
+
subscribe = (listener: () => void): (() => void) => { this.listeners.add(listener); return () => this.listeners.delete(listener) }
|
|
23
|
+
|
|
24
|
+
async open(sessionId: string, request?: AbortSignal): Promise<string> {
|
|
25
|
+
const signal = request === undefined ? this.lifetime.signal : AbortSignal.any([request, this.lifetime.signal])
|
|
26
|
+
signal.throwIfAborted()
|
|
27
|
+
if (!this.snapshot.available) throw new Error('官方浏览器尚未启用,请重新加载 Harness。')
|
|
28
|
+
// mounted changes before React commits. The Session's DOM seat confirms store adoption too.
|
|
29
|
+
const ready = () => this.ctx.sidebarRight.mounted.getSnapshot() === sessionId
|
|
30
|
+
&& [...document.querySelectorAll('[data-sidebar-right-session]')].some(node => node.getAttribute('data-sidebar-right-session') === sessionId)
|
|
31
|
+
if (!ready()) await new Promise<void>((resolve, reject) => {
|
|
32
|
+
const clean = () => { clearTimeout(timer); unsubscribe(); observer.disconnect(); signal.removeEventListener('abort', abort) }
|
|
33
|
+
const abort = () => { clean(); reject(signal.reason) }
|
|
34
|
+
const timer = setTimeout(() => { clean(); reject(new Error('工作区尚未就绪,请稍后重试。')) }, 10000)
|
|
35
|
+
const check = () => { if (ready()) { clean(); resolve() } }
|
|
36
|
+
const unsubscribe = this.ctx.sidebarRight.mounted.subscribe(check)
|
|
37
|
+
const observer = new MutationObserver(check)
|
|
38
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
39
|
+
signal.addEventListener('abort', abort, { once: true })
|
|
40
|
+
})
|
|
41
|
+
signal.throwIfAborted()
|
|
42
|
+
const before = new Set(this.ctx.sidebarRight.tabsIn(sessionId as SessionId).map(tab => tab.id))
|
|
43
|
+
this.ctx.sidebarRight.openTabIn(sessionId as SessionId, 'browser', { revealIfOpened: false })
|
|
44
|
+
const tab = this.ctx.sidebarRight.tabsIn(sessionId as SessionId).find(tab => tab.kind === 'browser' && !before.has(tab.id))
|
|
45
|
+
if (tab === undefined) throw new Error('浏览器页面未能打开,请重试。')
|
|
46
|
+
this.update()
|
|
47
|
+
return tab.id
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
focus(id: string): void {
|
|
51
|
+
this.ctx.sidebarRight.focus(id as TabId)
|
|
52
|
+
if (!this.ctx.sidebarRight.isExpanded()) this.ctx.sidebarRight.toggleExpanded()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
close(sessionId: string, id: string): void { this.ctx.sidebarRight.closeIn(sessionId as SessionId, id as TabId); this.update() }
|
|
56
|
+
dispose(): void { this.lifetime.abort(); for (const dispose of this.disposers) dispose(); this.listeners.clear() }
|
|
57
|
+
|
|
58
|
+
private update(): void {
|
|
59
|
+
this.snapshot = {
|
|
60
|
+
available: this.ctx.sidebarRightTabs.get('browser') !== undefined,
|
|
61
|
+
tabs: this.ctx.sidebarRight.openTabs.getSnapshot().filter(tab => tab.kind === 'browser').map(tab => ({
|
|
62
|
+
id: tab.tabId, sessionId: tab.sessionId,
|
|
63
|
+
title: this.ctx.sidebarRight.tabsIn(tab.sessionId).find(record => record.id === tab.tabId)?.title ?? '浏览器',
|
|
64
|
+
})),
|
|
65
|
+
}
|
|
66
|
+
for (const listener of this.listeners) listener()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Locate the central panel DSH renders when a global panel (Plugins, market…) is open. */
|
|
2
|
+
export function findDshGlobalPanel(documentRoot: Document = document): HTMLElement | null {
|
|
3
|
+
return documentRoot.querySelector<HTMLElement>('[data-global-panel]')
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const DSH_PANEL_TITLES: Record<string, string> = {
|
|
7
|
+
plugins: '插件',
|
|
8
|
+
'dsh-market': '插件市场',
|
|
9
|
+
automations: '自动化任务',
|
|
10
|
+
ssh: 'SSH',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Tab-style label for an embedded DSH panel; unknown ids keep their own name. */
|
|
14
|
+
export function dshPanelLabel(id: string): string {
|
|
15
|
+
return DSH_PANEL_TITLES[id] ?? id
|
|
16
|
+
}
|
package/src/client/index.tsx
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
2
|
+
import { ClientTerminals } from '@deepseek-ai/dsh-api-terminal-controller/client'
|
|
3
|
+
import type {} from '@deepseek-ai/dsh-api-terminal-controller/remote'
|
|
4
|
+
import type {} from '@deepseek-ai/dsh-api-gateway/client'
|
|
5
|
+
import { TerminalWorkspaces } from './terminal.ts'
|
|
6
|
+
import { NativeBrowser } from './browser.ts'
|
|
7
|
+
import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
|
|
2
8
|
import { useLayoutEffect } from 'react'
|
|
3
9
|
import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
|
|
4
10
|
import type { MainPanelId } from '@deepseek-ai/dsh-client-ui-layout/client'
|
|
@@ -12,7 +18,7 @@ import { WorkbenchController } from './service.ts'
|
|
|
12
18
|
import { TerminalPanel } from './TerminalPanel.tsx'
|
|
13
19
|
import { Workbench } from './Workbench.tsx'
|
|
14
20
|
import { installStyles } from './styles.ts'
|
|
15
|
-
import { handleTerminalShortcut } from './shortcuts.ts'
|
|
21
|
+
import { handleBrowserShortcut, handleTerminalShortcut } from './shortcuts.ts'
|
|
16
22
|
import { mainViewSession } from './session.ts'
|
|
17
23
|
import { createDshNavigation } from './dshNavigation.ts'
|
|
18
24
|
import type { DshNavigation } from './dshNavigation.ts'
|
|
@@ -21,10 +27,13 @@ export type { BottomPanelContribution, BottomPanelProps, VscodeWorkbench, Workbe
|
|
|
21
27
|
|
|
22
28
|
declare module '@deepseek-ai/cordis' { interface Context { vscodeWorkbench: VscodeWorkbench } }
|
|
23
29
|
|
|
24
|
-
const Overlay = ({ service, useSessions, navigation }: PropsRuntime<'shell.overlay'> & { service: VscodeWorkbench; navigation: DshNavigation }) => {
|
|
30
|
+
const Overlay = ({ service, browser, useSessions, usePanelInfo, navigation, openDshPanel, restoreConversation, closeDshPanel }: PropsRuntime<'shell.overlay'> & { service: VscodeWorkbench; browser: NativeBrowser; navigation: DshNavigation; openDshPanel(panelId: string): void; restoreConversation(): void; closeDshPanel(): void }) => {
|
|
25
31
|
const session = useSessions(mainViewSession)
|
|
26
32
|
const cwd = session?.projectionValues?.workingDirectory ?? session?.cwd
|
|
27
|
-
|
|
33
|
+
const activePanelId = usePanelInfo(info => info.activePanelId)
|
|
34
|
+
// DSH's own panel entry is not a page; every other global panel is shown inside the workbench.
|
|
35
|
+
const dshPanelId = activePanelId === null || activePanelId === WORKBENCH_PANEL_ID ? undefined : activePanelId
|
|
36
|
+
return <Workbench key={cwd ?? 'no-workspace'} service={service} browser={browser} sessionId={session?.id} cwd={cwd} navigation={navigation} dshPanelId={dshPanelId} onOpenDshPanel={openDshPanel} onRestoreConversation={restoreConversation} onCloseDshPanel={closeDshPanel}/>
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
const WORKBENCH_PANEL_ID = 'vscode-workbench' as MainPanelId
|
|
@@ -39,25 +48,35 @@ function WorkbenchPanel({ service, restoreConversation }: { service: VscodeWorkb
|
|
|
39
48
|
return null
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
export const inject = ['slots', 'layout']
|
|
51
|
+
export const inject = ['slots', 'layout', 'remote', 'remote.terminal', 'sidebarRight', 'sidebarRightTabs']
|
|
43
52
|
export function apply(ctx: ClientContext): void {
|
|
44
53
|
const service = new WorkbenchController()
|
|
54
|
+
const browser = new NativeBrowser(ctx)
|
|
55
|
+
ctx.effect(() => () => browser.dispose(), 'dsh-vscode-workbench: browser')
|
|
56
|
+
const terminals = new TerminalWorkspaces(new ClientTerminals(ctx.isolate('webTerminals'), ctx.remote.terminal))
|
|
57
|
+
ctx.effect(() => () => terminals.dispose(), 'dsh-vscode-workbench: terminal membership')
|
|
45
58
|
// Optional access to DSH's own workspace/session stores, used by the centered workspace switcher.
|
|
46
59
|
const navigation = createDshNavigation((name) => (ctx as unknown as { get?: (name: string) => unknown }).get?.(name))
|
|
47
60
|
ctx.effect(() => installStyles(), 'dsh-vscode-workbench: styles')
|
|
48
61
|
ctx.effect(() => { const dispose = ctx.reflect.provide('vscodeWorkbench', service); return () => { void dispose() } }, 'dsh-vscode-workbench: service')
|
|
49
|
-
ctx.effect(() => service.registerBottomPanel({ id: 'terminal', title: '终端', component: TerminalPanel }), 'dsh-vscode-workbench: terminal panel')
|
|
62
|
+
ctx.effect(() => service.registerBottomPanel({ id: 'terminal', title: '终端', component: props => <TerminalPanel key={props.sessionId} {...props} workspace={terminals.get(props.sessionId)}/> }), 'dsh-vscode-workbench: terminal panel')
|
|
50
63
|
ctx.effect(() => service.registerCommand({ id: 'terminal.new', run() { window.dispatchEvent(new Event('dvw-terminal-new')) } }), 'dsh-vscode-workbench: terminal command')
|
|
51
64
|
const restoreConversation = () => { ctx.layout?.selectPanel(null) }
|
|
65
|
+
// Re-open a panel that the workbench kept as a tab after handing the centre back.
|
|
66
|
+
const openDshPanel = (panelId: string) => {
|
|
67
|
+
try { ctx.layout?.selectPanel(panelId as MainPanelId) } catch { /* panel no longer registered */ }
|
|
68
|
+
}
|
|
69
|
+
const closeDshPanel = () => { ctx.layout?.selectPanel(null) }
|
|
52
70
|
ctx.slots.inject('main', () => ctx.slots.inject('sidebar.panellist', () => {
|
|
53
71
|
const disposeMain = ctx.slots.register({ name: 'main', key: WORKBENCH_PANEL_ID, inject: () => ({ service, restoreConversation }) }, WorkbenchPanel)
|
|
54
72
|
const disposeIcon = ctx.slots.register({ name: 'sidebar.panellist', id: WORKBENCH_PANEL_ID, order: 60, label: () => 'VS Code 工作台' }, WorkbenchPanelIcon)
|
|
55
73
|
return () => { disposeIcon(); disposeMain() }
|
|
56
74
|
}))
|
|
57
|
-
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dsh-vscode-workbench-overlay', inject: () => ({ service, navigation }) }, Overlay))
|
|
75
|
+
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dsh-vscode-workbench-overlay', inject: () => ({ service, browser, navigation, openDshPanel, restoreConversation, closeDshPanel }) }, Overlay))
|
|
58
76
|
ctx.effect(() => {
|
|
59
77
|
const onKey = (event: KeyboardEvent) => {
|
|
60
78
|
if (handleTerminalShortcut(event, service)) return
|
|
79
|
+
if (handleBrowserShortcut(event, service)) return
|
|
61
80
|
if (event.isComposing || !event.ctrlKey || event.altKey) return
|
|
62
81
|
const view = event.shiftKey && event.code === 'KeyE' ? 'explorer' : event.shiftKey && event.code === 'KeyF' ? 'search' : event.shiftKey && event.code === 'KeyG' ? 'git' : undefined
|
|
63
82
|
if (view !== undefined) { event.preventDefault(); service.show(view) }
|
package/src/client/shortcuts.ts
CHANGED
|
@@ -31,3 +31,12 @@ export function handleTerminalShortcut(event: KeyboardEvent, workbench: VscodeWo
|
|
|
31
31
|
}
|
|
32
32
|
return true
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
/** Match the official desktop browser shortcut while the workbench owns focus. */
|
|
36
|
+
export function handleBrowserShortcut(event: KeyboardEvent, workbench: VscodeWorkbench): boolean {
|
|
37
|
+
if (!workbench.getSnapshot().visible || event.isComposing || !(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey || event.code !== 'KeyT') return false
|
|
38
|
+
event.preventDefault()
|
|
39
|
+
event.stopImmediatePropagation()
|
|
40
|
+
window.dispatchEvent(new Event('dvw-browser-new'))
|
|
41
|
+
return true
|
|
42
|
+
}
|
package/src/client/styles.ts
CHANGED
|
@@ -17,8 +17,8 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
|
|
|
17
17
|
.dvw-overlay[data-dvw-theme=dark]{--dvw-base:#191a1b;--dvw-surface-1:#121314;--dvw-surface-2:#202122;--dvw-surface-3:#252526;--dvw-side:#191a1b;--dvw-tab-active:#2a2b2c;--dvw-gutter:#191a1b;--dvw-graph-ref:#3794ff;--dvw-text:#bfbfbf;--dvw-text-2:#8c8c8c;--dvw-text-3:#6e6e6e;--dvw-border:#2a2b2c;--dvw-border-soft:#242526;--dvw-hover:#ffffff12;--dvw-active:#ffffff1f;--dvw-accent:#307e9f;--dvw-error:#e06c75;--dvw-success:#98c379;--dvw-warn:#e5c07b}
|
|
18
18
|
.dvw-overlay[data-dvw-theme=light]{--dvw-base:#f9fafb;--dvw-surface-1:#fff;--dvw-surface-2:#fff;--dvw-surface-3:#fff;--dvw-side:#f9fafb;--dvw-tab-active:#e8e8e8;--dvw-gutter:#f9fafb;--dvw-graph-ref:#0063d3;--dvw-text:#1f1f1f;--dvw-text-2:#616161;--dvw-text-3:#8a8a8a;--dvw-border:#e5e5e5;--dvw-border-soft:#ececec;--dvw-hover:#0000000d;--dvw-active:#00000014;--dvw-accent:#0078d4;--dvw-error:#e51400;--dvw-success:#388a34;--dvw-warn:#bf8803;color-scheme:light}
|
|
19
19
|
.dvw-overlay *{box-sizing:border-box}.dvw-overlay ::-webkit-scrollbar{width:10px;height:10px}.dvw-overlay ::-webkit-scrollbar-track{background:transparent}.dvw-overlay ::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--dvw-text) 20%,transparent);border:2px solid transparent;background-clip:padding-box;border-radius:6px}.dvw-overlay ::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--dvw-text) 32%,transparent);background-clip:padding-box}.dvw-overlay ::-webkit-scrollbar-corner{background:transparent}.dvw-activity{grid-row:1/3;background:var(--dvw-base);border:1px solid var(--dvw-border);border-radius:8px 0 0 8px;display:flex;flex-direction:column;align-items:center;overflow:hidden}.dvw-activity-menu{min-width:220px}.dvw-activity-empty{flex:1}.dvw-activity-bottom{width:100%;margin-top:auto;display:flex;flex-direction:column;align-items:center}.dvw-menu-separator{height:1px;margin:4px 0;background:var(--dvw-border-soft)}.dvw-menu-check{display:inline-block;width:14px;flex:none;color:var(--dvw-text)}.dvw-act{touch-action:none;user-select:none}.dvw-act[data-dragging=true]{opacity:.35}.dvw-activity-ghost{position:fixed;z-index:120;width:48px;height:48px;display:grid;place-items:center;pointer-events:none;border-radius:8px;background:var(--dvw-tab-active);color:var(--dvw-text);font-size:22px;box-shadow:0 8px 22px rgba(0,0,0,.3);transform:translate(-50%,-50%) scale(1.08);opacity:.95}.dvw-activity-ghost .codicon{font-size:18px}.dvw-act{position:relative;width:48px;height:48px;padding:0;border:0;background:transparent;color:var(--dvw-text-3);font-size:22px;cursor:pointer;display:grid;place-items:center}.dvw-act::before{content:"";position:absolute;left:50%;top:50%;width:32px;height:32px;border-radius:6px;background:transparent;transform:translate(-50%,-50%)}.dvw-act .codicon,.dvw-act svg{position:relative;z-index:1}.dvw-act .codicon{font-size:18px}.dvw-act:hover,.dvw-act[data-active=true]{color:var(--dvw-text)}.dvw-act:hover::before{background:var(--dvw-hover)}.dvw-act[data-active=true]::before{background:var(--dvw-tab-active)}.dvw-act.dsh{margin-top:auto}.dvw-side{grid-column:2;grid-row:1/3;min-width:0;width:100%;background:var(--dvw-side);border-top:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-top-right-radius:8px;border-bottom-right-radius:8px;overflow:hidden;display:flex;flex-direction:column}.dvw-side-resizer{grid-column:2;grid-row:1/3;justify-self:end;width:4px;height:100%;z-index:4;cursor:ew-resize;transform:translateX(2px);touch-action:none}.dvw-side-resizer:hover,.dvw-side-resizer:focus-visible,.dvw-side-resizer[data-dragging=true]{background:var(--dvw-accent)}.dvw-side-head{height:35px;display:flex;align-items:center;padding:0 12px;text-transform:uppercase;font-size:11px;letter-spacing:.4px}.dvw-toolbar{margin-left:auto;display:flex;gap:2px}.dvw-icon{border:0;background:transparent;color:var(--dvw-text-2);border-radius:4px;min-width:24px;height:24px;display:inline-grid;place-items:center;cursor:pointer}.dvw-icon:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-icon:disabled{opacity:.45;cursor:default}.dvw-file-picker{display:none}.dvw-tree,.dvw-results,.dvw-git{overflow:auto;flex:1}.dvw-tree[data-drop-target=true]{outline:1px dashed var(--dvw-accent);outline-offset:-3px}.dvw-tree-row{display:flex;align-items:center;height:22px;padding-right:6px;cursor:pointer;white-space:nowrap}.dvw-tree-row:hover{background:var(--dvw-hover)}.dvw-tree-row[data-selected=true],.dvw-tree-row[data-drop-target=true]{background:var(--dvw-active)}.dvw-chevron{width:16px;flex:none;text-align:center;color:var(--dvw-text-3)}.dvw-file-icon{width:16px;height:16px;flex:none;display:block;object-fit:contain;color:var(--dvw-text-2)}.dvw-file-icon.folder{color:var(--dvw-accent)}.dvw-tree-name{overflow:hidden;text-overflow:ellipsis}.dvw-tree-rename,.dvw-tree-create-input{min-width:0;flex:1;height:20px;border:1px solid var(--dvw-accent);border-radius:2px;background:var(--dvw-surface-1);color:var(--dvw-text);padding:0 3px;font:inherit;letter-spacing:0;outline:0}.dvw-tree-create-row{cursor:text}.dvw-tree-create-row:hover{background:transparent}.dvw-tree-create-input::placeholder{color:var(--dvw-text-3)}.dvw-tree-create-input[aria-invalid=true]{border-color:var(--dvw-error)}.dvw-tree-create-error{min-height:20px;padding-top:2px;padding-right:6px;padding-bottom:3px;color:var(--dvw-error);font-size:11px;line-height:15px;white-space:normal;overflow-wrap:anywhere}.dvw-tree-status{margin-left:auto;padding-left:6px;font-size:11px;font-weight:600;color:var(--dvw-warn)}.dvw-tree-status[data-status=M]{color:var(--dvw-warn)}.dvw-tree-status[data-status=A]{color:var(--dvw-success)}.dvw-tree-status[data-status=D]{color:var(--dvw-error)}.dvw-context-menu{position:fixed;z-index:40;min-width:148px;padding:4px;border:1px solid var(--dvw-border);border-radius:5px;background:var(--dvw-surface-3);box-shadow:0 6px 20px rgba(0,0,0,.3)}.dvw-context-menu button{width:100%;height:28px;border:0;border-radius:3px;background:transparent;color:var(--dvw-text);display:flex;align-items:center;gap:8px;padding:0 8px;text-align:left;font:inherit;cursor:pointer}.dvw-context-menu button:hover{background:var(--dvw-hover)}.dvw-context-menu .dvw-context-delete{color:var(--dvw-error)}.dvw-tab-menu{min-width:220px}.dvw-tab-menu button{justify-content:space-between;gap:24px}.dvw-tab-menu button span{color:var(--dvw-text-3);font-size:11px}.dvw-tab-menu button:disabled{opacity:.45;cursor:default}.dvw-tab-menu button:disabled:hover{background:transparent}
|
|
20
|
-
.dvw-main{grid-column:3;grid-row:1;position:relative;display:flex;min-width:0;min-height:0;flex-direction:column;background:var(--dvw-surface-1);border-left:1px solid var(--dvw-border);border-top:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-radius:8px;margin-left:
|
|
21
|
-
.dvw-bottom-resizer{height:4px;cursor:ns-resize}.dvw-bottom-resizer:hover{background:var(--dvw-accent)}.dvw-bottom-reveal{position:absolute;left:0;right:0;bottom:0;height:6px;z-index:4;cursor:ns-resize}.dvw-bottom-reveal::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:transparent}.dvw-bottom-reveal:hover::after{background:var(--dvw-accent)}.dvw-bottom{grid-column:3;grid-row:2;height:260px;min-height:120px;border-top:1px solid var(--dvw-border);border-left:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-radius:8px;margin-top:4px;margin-left:
|
|
20
|
+
.dvw-main{grid-column:3;grid-row:1;position:relative;display:flex;min-width:0;min-height:0;flex-direction:column;background:var(--dvw-surface-1);border-left:1px solid var(--dvw-border);border-top:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-radius:8px;margin-left:3px;overflow:hidden}.dvw-tabs{height:35px;background:linear-gradient(to top,var(--dvw-border) 0 1px,var(--dvw-base) 1px 100%);display:flex;overflow:hidden}.dvw-tabs[data-empty]{display:none}.dvw-tab{position:relative;height:35px;margin:0;min-width:110px;max-width:260px;border:0;border-right:1px solid var(--dvw-border-soft);border-radius:8px 8px 0 0;background:transparent;color:var(--dvw-text-2);padding:0 8px 0 12px;display:flex;align-items:center;gap:6px;cursor:pointer;flex:none;white-space:nowrap}.dvw-tab:hover{background:var(--dvw-hover)}.dvw-tab[data-active=true]{background:var(--dvw-surface-1);color:var(--dvw-text);border-top:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border)}.dvw-tab-icon{width:16px;height:16px;flex:none;object-fit:contain}.dvw-tab-close{width:16px;height:16px;flex:none;display:grid;place-items:center;border-radius:3px;opacity:0}.dvw-tab-close:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-tab[data-active=true] .dvw-tab-close,.dvw-tab:hover .dvw-tab-close{opacity:1}.dvw-tab-name{overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0;text-align:left;white-space:nowrap}.dvw-editor{position:relative;flex:1;min-height:0}.dvw-editor-toolbar{position:absolute;z-index:3;top:0;right:0;left:0;height:35px;display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:0 10px;border-bottom:1px solid var(--dvw-border-soft);background:var(--dvw-surface-1)}.dvw-editor-body{position:absolute;inset:0;min-height:0}.dvw-editor-body[data-toolbar=true]{top:35px}.dvw-mode-button{height:27px;padding:0 10px;display:flex;align-items:center;gap:6px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--dvw-text-2);cursor:pointer}.dvw-mode-button:hover{background:var(--dvw-hover)}.dvw-mode-button[data-active=true]{border-color:var(--dvw-border);background:var(--dvw-active);color:var(--dvw-text)}.dvw-empty{height:100%;display:grid;place-items:center;color:var(--dvw-text-3);text-align:center}.dvw-empty strong{font-size:24px;color:var(--dvw-text-2)}.dvw-message{padding:12px;color:var(--dvw-warn)}.dvw-message button{margin-left:10px;padding:2px 10px;border:1px solid var(--dvw-border);border-radius:4px;background:var(--dvw-surface-2);color:var(--dvw-text);font:inherit;cursor:pointer}.dvw-message button:hover{background:var(--dvw-hover)}
|
|
21
|
+
.dvw-bottom-resizer{height:4px;cursor:ns-resize}.dvw-bottom-resizer:hover{background:var(--dvw-accent)}.dvw-bottom-reveal{position:absolute;left:0;right:0;bottom:0;height:6px;z-index:4;cursor:ns-resize}.dvw-bottom-reveal::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:transparent}.dvw-bottom-reveal:hover::after{background:var(--dvw-accent)}.dvw-bottom{grid-column:3;grid-row:2;height:260px;min-height:120px;border-top:1px solid var(--dvw-border);border-left:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-radius:8px;margin-top:4px;margin-left:3px;background:var(--dvw-base);display:flex;flex-direction:column}.dvw-bottom-head{height:35px;display:flex;align-items:center;gap:14px;padding:0 12px}.dvw-bottom-title{border:0;border-bottom:1px solid var(--dvw-text);background:transparent;color:var(--dvw-text);height:35px}.dvw-bottom-body{flex:1;min-height:0}.dvw-status{grid-column:1/4;grid-row:3;background:var(--dvw-base);color:var(--dvw-text);display:flex;align-items:center;padding:6px 8px 0;gap:18px;font-size:12px}.dvw-theme-control{position:relative;margin-left:auto}.dvw-theme-trigger{height:22px;min-width:112px;display:flex;align-items:center;gap:7px;padding:0 8px;border:0;border-radius:5px;background:color-mix(in srgb,#000 17%,transparent);color:#fff;cursor:pointer}.dvw-theme-trigger:hover{background:color-mix(in srgb,#000 27%,transparent)}.dvw-theme-orb{width:13px;height:13px;border:2px solid #fff;border-radius:50%;background:linear-gradient(90deg,#191a1b 50%,#fff 50%)}.dvw-theme-orb.dark{background:#191a1b}.dvw-theme-orb.light{background:#fff}.dvw-theme-menu{position:absolute;right:0;bottom:29px;width:224px;padding:6px;border:1px solid var(--dvw-border);border-radius:10px;background:var(--dvw-surface-3);box-shadow:0 10px 35px rgba(0,0,0,.28);color:var(--dvw-text)}.dvw-theme-menu button{width:100%;min-height:48px;border:0;border-radius:7px;background:transparent;color:inherit;display:grid;grid-template-columns:30px 1fr 20px;align-items:center;text-align:left;padding:6px;cursor:pointer}.dvw-theme-menu button:hover,.dvw-theme-menu button[aria-checked=true]{background:var(--dvw-hover)}.dvw-theme-menu strong,.dvw-theme-menu small{display:block}.dvw-theme-menu small{color:var(--dvw-text-3);margin-top:2px}.dvw-theme-swatch{width:22px;height:22px;border-radius:6px;border:1px solid var(--dvw-border);background:linear-gradient(135deg,#191a1b 0 50%,#fff 50%)}.dvw-theme-swatch.dark{background:#191a1b}.dvw-theme-swatch.light{background:#fff}
|
|
22
22
|
.dvw-search-form{padding:8px;display:grid;gap:6px}.dvw-input,.dvw-textarea,.dvw-select{width:100%;border:1px solid var(--dvw-border);border-radius:2px;background:var(--dvw-surface-2);color:var(--dvw-text);padding:3px 7px;outline:none}.dvw-input:focus,.dvw-textarea:focus,.dvw-select:focus{border-color:var(--dvw-accent)}.dvw-options{display:flex;align-items:center;gap:8px;font-size:11px}.dvw-result-file{height:22px;display:flex;align-items:center;gap:4px;padding:0 8px;font-weight:600}.dvw-result{height:22px;align-items:center;padding:0 8px 0 20px;cursor:pointer;display:flex;gap:8px}.dvw-result:hover{background:var(--dvw-hover)}.dvw-line{color:var(--dvw-text-3);min-width:30px;text-align:right}.dvw-preview{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-search-hit{background:color-mix(in srgb,var(--dvw-accent) 28%,transparent);border-radius:2px}
|
|
23
23
|
.dvw-git-head{padding:7px 10px;border-bottom:1px solid var(--dvw-border);display:flex;align-items:center;gap:8px;position:relative}.dvw-branch{min-width:0;border:0;background:transparent;color:var(--dvw-text);font:inherit;font-weight:600;display:flex;align-items:center;gap:5px;cursor:pointer}.dvw-branch-caret{margin-left:2px;font-size:14px;color:var(--dvw-text-3)}.dvw-branch-menu{position:absolute;z-index:12;top:calc(100% + 2px);left:6px;right:6px;padding:4px;border:1px solid var(--dvw-border);border-radius:8px;background:var(--dvw-surface-2);box-shadow:0 10px 28px rgba(0,0,0,.28);color:var(--dvw-text)}.dvw-branch-current{padding:4px 8px;color:var(--dvw-text-3);font-size:11px}.dvw-branch-list{max-height:min(300px,45vh);overflow:auto}.dvw-branch-list button{width:100%;height:26px;display:flex;align-items:center;gap:6px;border:0;border-radius:4px;background:transparent;color:var(--dvw-text);padding:0 8px;text-align:left;font:inherit;cursor:pointer}.dvw-branch-list button:hover{background:var(--dvw-hover)}.dvw-branch-list button[data-active=true]{background:var(--dvw-active)}.dvw-branch-list button span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-branch-worktree{margin-left:auto;flex:none;padding:1px 5px;border-radius:8px;background:var(--dvw-surface-3);color:var(--dvw-text-3);font-size:10px;line-height:14px}.dvw-branch-create{display:flex;gap:4px;align-items:center;margin-top:4px;padding-top:6px;border-top:1px solid var(--dvw-border-soft)}.dvw-branch-create .dvw-input{flex:1;min-width:0}.dvw-branch-create-button{flex:none;width:24px;min-width:24px;height:24px;border:1px solid var(--dvw-border);border-radius:4px}.dvw-commit{display:flex;flex-direction:column;gap:6px;padding:9px}.dvw-commit-actions{display:grid;grid-template-columns:1fr auto;gap:6px}.dvw-commit-button{height:31px;border:0;border-radius:6px;background:color-mix(in srgb,var(--dvw-accent) 24%,var(--dvw-surface-2));color:var(--dvw-text);display:flex;align-items:center;justify-content:center;gap:7px;cursor:pointer}.dvw-commit-button:hover{background:color-mix(in srgb,var(--dvw-accent) 34%,var(--dvw-surface-2))}.dvw-commit-button:disabled{opacity:.45;cursor:default}.dvw-commit-ai{height:31px;border:1px solid var(--dvw-border);border-radius:6px;background:var(--dvw-surface-2);color:var(--dvw-text-2);display:flex;align-items:center;justify-content:center;gap:6px;font:inherit;cursor:pointer}.dvw-commit-ai:hover:not(:disabled){background:var(--dvw-hover);color:var(--dvw-text)}.dvw-commit-ai:disabled{opacity:.45;cursor:default}.dvw-group-head{height:30px;display:flex;align-items:center;gap:5px;padding:0 6px 0 9px;font-weight:600;position:sticky;top:0;background:var(--dvw-side);z-index:1}.dvw-group-head:hover{background:var(--dvw-hover)}.dvw-group-head .dvw-count{margin-left:auto;min-width:20px;height:20px;border-radius:10px;display:grid;place-items:center;background:var(--dvw-surface-3);color:var(--dvw-text-2);font-size:11px}.dvw-change{display:flex;align-items:center;height:22px;padding:0 5px 0 19px;gap:5px;cursor:pointer}.dvw-change:hover{background:var(--dvw-hover)}.dvw-change .dvw-icon{opacity:0}.dvw-change:hover .dvw-icon,.dvw-change:focus-within .dvw-icon{opacity:1}.dvw-change-path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.dvw-badge{font-size:11px;color:var(--dvw-warn)}.dvw-graph{border-top:1px solid var(--dvw-border-soft);margin-top:8px;padding-top:3px}.dvw-graph-list{padding-bottom:8px}.dvw-graph-row{width:100%;height:38px;border:0;background:transparent;color:var(--dvw-text);display:flex;align-items:center;padding:0 7px;text-align:left;cursor:pointer;overflow:hidden}.dvw-graph-row:hover{background:var(--dvw-hover)}.dvw-graph-svg{flex:none;align-self:stretch}.dvw-graph-line{fill:none;stroke-width:1px;stroke-linecap:round;vector-effect:non-scaling-stroke}.dvw-graph-node{stroke:var(--dvw-side);stroke-width:2px;fill:var(--dvw-side)}.dvw-graph-copy{display:flex;min-width:0;flex:1;flex-direction:column;justify-content:center}.dvw-graph-subject{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-graph-meta{font-size:11px;color:var(--dvw-text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-ref{max-width:82px;margin-left:4px;padding:1px 5px;border:1px solid color-mix(in srgb,var(--dvw-accent) 50%,transparent);border-radius:9px;color:var(--dvw-accent);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-error{padding:8px;color:var(--dvw-error);white-space:pre-wrap}.dvw-diff{margin:0;height:100%;overflow:auto;padding:8px 0;background:var(--dvw-surface-1);font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre}.dvw-diff-line{display:block;min-height:18px;padding:0 14px}.dvw-diff-line.add{background:color-mix(in srgb,var(--dvw-success) 15%,transparent);color:var(--dvw-success)}.dvw-diff-line.remove{background:color-mix(in srgb,var(--dvw-error) 13%,transparent);color:var(--dvw-error)}.dvw-diff-line.hunk{background:color-mix(in srgb,var(--dvw-accent) 12%,transparent);color:var(--dvw-accent)}.dvw-diff-line.meta{color:var(--dvw-text-3)}.dvw-diff-empty{padding:24px;color:var(--dvw-text-3)}
|
|
24
24
|
.dvw-tab-list{display:flex;min-width:0;flex:1;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;scrollbar-width:none}.dvw-tab-list::-webkit-scrollbar{display:none}.dvw-monaco-diff{position:absolute;inset:0}.dvw-diff-toolbar{justify-content:flex-end}.dvw-group-head{gap:2px;padding-left:2px}.dvw-group-toggle{min-width:0;height:30px;flex:1;border:0;border-radius:5px;background:transparent;color:var(--dvw-text);font:inherit;font-weight:600;display:flex;align-items:center;gap:5px;padding:0 7px;cursor:pointer;text-align:left}.dvw-group-toggle:hover{background:var(--dvw-hover)}.dvw-graph-entry[data-expanded=true]{background:color-mix(in srgb,var(--dvw-accent) 5%,transparent)}.dvw-commit-files{padding:1px 0 5px 26px}.dvw-commit-file{width:100%;height:22px;border:0;background:transparent;color:var(--dvw-text);display:flex;align-items:center;gap:6px;padding:0 7px;text-align:left;cursor:pointer}.dvw-commit-file:hover{background:var(--dvw-hover)}.dvw-commit-file-name{white-space:nowrap}.dvw-commit-file-dir{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dvw-text-3);font-size:11px}
|
|
@@ -31,7 +31,7 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
|
|
|
31
31
|
.dvw-overlay[data-dvw-chat=true]:not([data-dvw-primary-side=true]){grid-template-columns:48px 0 minmax(0,1fr) var(--dvw-chat-width)}
|
|
32
32
|
.dvw-overlay:not([data-dvw-primary-side=true]) .dvw-side{visibility:hidden;border-right:0}.dvw-overlay:not([data-dvw-primary-side=true]) .dvw-activity{border-radius:8px}.dvw-overlay:not([data-dvw-primary-side=true]) .dvw-side-resizer{display:none}
|
|
33
33
|
.dvw-overlay:not([data-dvw-status=true]){grid-template-rows:minmax(0,1fr) auto 6px}.dvw-overlay:not([data-dvw-status=true]) .dvw-status{display:none}
|
|
34
|
-
.dvw-overlay[data-dvw-chat=true] .dvw-status{grid-column:1/-1;padding-right:calc(var(--dvw-chat-width) +
|
|
34
|
+
.dvw-overlay[data-dvw-chat=true] .dvw-status{grid-column:1/-1;padding-right:calc(var(--dvw-chat-width) + 6px)}
|
|
35
35
|
.dvw-chat{grid-column:4;grid-row:1/3;min-width:0;background:var(--dvw-gutter);border-left:1px solid var(--dvw-border);position:relative}
|
|
36
36
|
.dvw-chat-head{height:36px;display:flex;align-items:center;justify-content:space-between;padding:0 6px 0 12px;border-bottom:1px solid var(--dvw-border);background:var(--dvw-base);font-weight:600}
|
|
37
37
|
.dvw-chat-head>span{display:flex;align-items:center;gap:7px}.dvw-chat-body{height:calc(100% - 36px);background:var(--dvw-base)}
|
|
@@ -47,7 +47,7 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
|
|
|
47
47
|
.dvw-chat{border-left-color:transparent!important}
|
|
48
48
|
.dvw-side-resizer::after,.dvw-chat-resizer::after{background:transparent;left:3px;width:4px}
|
|
49
49
|
.dvw-side-resizer:hover::after,.dvw-side-resizer:focus-visible::after,.dvw-side-resizer[data-dragging=true]::after,.dvw-chat-resizer:hover::after,.dvw-chat-resizer:focus-visible::after,.dvw-chat-resizer[data-dragging=true]::after{background:var(--dvw-accent)}
|
|
50
|
-
[data-dvw-conversation-panel]{position:fixed!important;top:calc(var(--dvw-titlebar-height) + 6px)!important;right:
|
|
50
|
+
[data-dvw-conversation-panel]{position:fixed!important;top:calc(var(--dvw-titlebar-height) + 6px)!important;right:3px!important;bottom:var(--dvw-status-height,30px)!important;left:auto!important;width:calc(var(--dvw-chat-width) - 6px)!important;height:auto!important;min-width:0!important;z-index:21!important;border:1px solid var(--dsw-alias-border-l2)!important;border-radius:8px!important;overflow:hidden!important;box-shadow:none!important}
|
|
51
51
|
html[data-platform=darwin] [data-dvw-conversation-panel]{top:var(--dvw-titlebar-height)!important}
|
|
52
52
|
body[data-dvw-palette=light] [data-dvw-conversation-panel]{--dsw-alias-bg-base:#f9fafb;--dsw-specific-sidebar-fill:#f9fafb;--dsw-alias-border-l2:#e5e5e5;background:#f9fafb!important}body[data-dvw-palette=dark] [data-dvw-conversation-panel]{--dsw-alias-bg-base:#191a1b;--dsw-specific-sidebar-fill:#191a1b;--dsw-alias-bg-layer-1:#202122;--dsw-alias-bg-layer-2:#252526;--dsw-alias-bg-layer-3:#2a2b2c;--dsw-alias-label-primary:#bfbfbf;--dsw-alias-label-secondary:#8c8c8c;--dsw-alias-label-tertiary:#6e6e6e;--dsw-alias-border-l2:#2a2b2c;--dsw-alias-border-l3:#303132;--dsw-alias-interactive-bg-hover:#ffffff12;--dsw-alias-interactive-bg-active:#ffffff1f}
|
|
53
53
|
body[data-dvw-workbench-active] [data-sidebar-right-expand],body[data-dvw-conversation-active] [data-sidebar-right-expand]{display:none!important}
|
|
@@ -69,7 +69,7 @@ body[data-dvw-workbench-active] [data-sidebar-toggle]{display:none!important}
|
|
|
69
69
|
.dvw-settings-themes{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.dvw-settings-themes button{height:56px;display:grid;grid-template-columns:28px 1fr 18px;align-items:center;gap:7px;padding:0 9px;border:1px solid var(--dvw-border);border-radius:7px;background:var(--dvw-base);color:var(--dvw-text);text-align:left;cursor:pointer}.dvw-settings-themes button:hover{background:var(--dvw-hover)}.dvw-settings-themes button[data-active=true]{border-color:var(--dvw-accent);background:color-mix(in srgb,var(--dvw-accent) 10%,var(--dvw-base))}
|
|
70
70
|
.dvw-setting-check{height:36px;display:flex;align-items:center;gap:9px;border-bottom:1px solid var(--dvw-border-soft);cursor:pointer}.dvw-setting-check input{accent-color:var(--dvw-accent)}.dvw-setting-range{display:grid;grid-template-columns:145px 1fr 52px;align-items:center;gap:10px;min-height:42px}.dvw-setting-range input{accent-color:var(--dvw-accent)}.dvw-setting-range output{text-align:right;color:var(--dvw-text-3);font-variant-numeric:tabular-nums}
|
|
71
71
|
.dvw-setting-choice{display:grid;grid-template-columns:145px 1fr;align-items:center;gap:10px;min-height:42px}.dvw-setting-choice>span{color:var(--dvw-text-2);font-size:12px}.dvw-setting-segment{display:inline-flex;justify-self:start;padding:2px;border:1px solid var(--dvw-border);border-radius:6px;background:var(--dvw-base)}.dvw-setting-segment button{min-width:64px;height:26px;border:0;border-radius:4px;background:transparent;color:var(--dvw-text-2);font:inherit;font-size:12px;cursor:pointer}.dvw-setting-segment button:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-setting-segment button[data-active=true]{background:var(--dvw-active);color:var(--dvw-text)}
|
|
72
|
-
@media(max-width:900px){.dvw-overlay[data-dvw-chat=true]{--dvw-chat-effective:max(440px,min(var(--dvw-chat-width),calc(100vw - 254px)));grid-template-columns:44px min(210px,var(--dvw-side-width),max(0px,calc(100vw - 164px - var(--dvw-chat-effective)))) minmax(0,1fr) var(--dvw-chat-effective)}.dvw-overlay[data-dvw-chat=true] .dvw-status{padding-right:calc(var(--dvw-chat-effective) +
|
|
72
|
+
@media(max-width:900px){.dvw-overlay[data-dvw-chat=true]{--dvw-chat-effective:max(440px,min(var(--dvw-chat-width),calc(100vw - 254px)));grid-template-columns:44px min(210px,var(--dvw-side-width),max(0px,calc(100vw - 164px - var(--dvw-chat-effective)))) minmax(0,1fr) var(--dvw-chat-effective)}.dvw-overlay[data-dvw-chat=true] .dvw-status{padding-right:calc(var(--dvw-chat-effective) + 6px)}.dvw-chat-resizer{right:calc(var(--dvw-chat-effective) - 5px)}[data-dvw-conversation-panel]{width:calc(max(440px,min(var(--dvw-chat-width),calc(100vw - 254px))) - 6px)!important}}
|
|
73
73
|
.dvw-setting-textarea{width:100%;min-height:84px;resize:vertical;padding:8px 10px;border:1px solid var(--dvw-border);border-radius:6px;background:var(--dvw-base);color:var(--dvw-text);font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
74
74
|
.dvw-setting-textarea:focus{outline:none;border-color:var(--dvw-accent)}
|
|
75
75
|
.dvw-setting-extension{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--dvw-border);border-radius:7px;background:var(--dvw-base)}.dvw-setting-extension>div{min-width:0;flex:1;display:flex;flex-direction:column;gap:2px}.dvw-setting-extension span{color:var(--dvw-text-3);font-size:12px}.dvw-setting-extension button{height:26px;padding:0 14px;border:1px solid var(--dvw-border);border-radius:4px;background:transparent;color:var(--dvw-text);font:inherit;cursor:pointer}.dvw-setting-extension button:hover{background:var(--dvw-hover)}.dvw-setting-extension button.dvw-primary-button{border-color:var(--dvw-accent);background:var(--dvw-accent);color:#fff}.dvw-setting-hint{margin:8px 0 0;color:var(--dvw-text-3);font-size:11px;line-height:1.5}
|
|
@@ -94,9 +94,10 @@ body[data-dvw-workbench-active] [data-sidebar-toggle]{display:none!important}
|
|
|
94
94
|
.dvw-tab{border-radius:10px 10px 0 0;border-right:0}
|
|
95
95
|
.dvw-tabs{background:color-mix(in srgb,var(--dvw-text) 4%,var(--dvw-base))}
|
|
96
96
|
.dvw-tab[data-active=true]{border-top:0;border-right-color:transparent}
|
|
97
|
+
.dvw-tab:hover:not([data-active=true]){background:color-mix(in srgb,var(--dvw-text) 3%,transparent)}
|
|
97
98
|
.dvw-tab[data-active=true]::before,.dvw-tab[data-active=true]::after{content:"";position:absolute;bottom:0;width:10px;height:10px;background:var(--dvw-surface-1);pointer-events:none}
|
|
98
|
-
.dvw-tab[data-active=true]::before{left:-10px
|
|
99
|
-
.dvw-tab[data-active=true]::after{right:-10px
|
|
99
|
+
.dvw-tab[data-active=true]::before{left:-10px;clip-path:path("M10 0C10 5.5 5.5 10 0 10L10 10Z")}
|
|
100
|
+
.dvw-tab[data-active=true]::after{right:-10px;clip-path:path("M0 0C0 5.5 4.5 10 10 10L0 10Z")}
|
|
100
101
|
.dvw-commit-box{position:relative;display:flex;align-items:stretch}
|
|
101
102
|
.dvw-commit-input{width:100%;min-height:30px;max-height:96px;field-sizing:content;resize:none;padding:5px 32px 5px 8px;line-height:18px}
|
|
102
103
|
.dvw-commit-ai{position:absolute;right:4px;top:50%;transform:translateY(-50%);width:24px;min-width:24px;height:24px;border:0;background:transparent;color:var(--dvw-text-3)}
|
|
@@ -188,6 +189,16 @@ body[data-dvw-palette=eyecare] [data-dvw-conversation-panel]{--dsw-alias-bg-base
|
|
|
188
189
|
.dvw-workspace-switcher>footer button{height:32px;border-radius:9px;padding:0 16px}
|
|
189
190
|
.dvw-workspace-switcher .dvw-icon{border-radius:9px}
|
|
190
191
|
@media(max-width:760px){.dvw-settings-body{grid-template-columns:1fr;grid-template-rows:auto minmax(0,1fr)}.dvw-settings-nav{grid-column:1;grid-row:1;flex-direction:row;border-right:0;border-bottom:1px solid var(--dvw-border-soft);padding:8px}.dvw-settings-nav button{flex:none}.dvw-settings-body>section{grid-column:1;grid-row:2;padding:16px 18px 24px}}
|
|
192
|
+
.dvw-dsh-tab-mark{width:16px;height:16px;flex:none;background:currentColor;-webkit-mask:${DSH_MARK_MASK} center/contain no-repeat;mask:${DSH_MARK_MASK} center/contain no-repeat}
|
|
193
|
+
.codicon-globe:before{content:"\eb01"}
|
|
194
|
+
.dvw-terminal-pane{display:flex;flex:1;min-width:0;min-height:0;flex-direction:column}.dvw-terminal-panes[data-split=true]>.dvw-terminal-pane+.dvw-terminal-pane{border-left:1px solid var(--dvw-border)}
|
|
195
|
+
.dvw-terminal-notice{display:flex;align-items:center;gap:8px;padding:4px 10px;color:var(--dvw-text-2);font-size:12px}.dvw-terminal-notice button,.dvw-start-tools button{border:1px solid var(--dvw-border);border-radius:4px;background:var(--dvw-surface-2);color:var(--dvw-text);font:inherit;cursor:pointer}.dvw-terminal-notice button:hover,.dvw-start-tools button:hover{background:var(--dvw-hover)}
|
|
196
|
+
.dvw-start-tools{display:flex;justify-content:center;gap:10px;margin-top:20px}.dvw-start-tools button{display:flex;align-items:center;gap:8px;padding:8px 16px}.dvw-start-tools button:disabled{opacity:.45;cursor:default}
|
|
197
|
+
.dvw-editor[data-browser-active]>.dvw-editor-body,.dvw-editor[data-browser-active]>.dvw-editor-toolbar,.dvw-editor[data-browser-active]>.dvw-outline{visibility:hidden}
|
|
198
|
+
[data-dvw-browser-page]:popover-open{position:fixed!important;inset:auto!important;margin:0!important;padding:0!important;border:0!important;border-radius:0!important;box-sizing:border-box!important;overflow:hidden!important;background:var(--dsw-alias-bg-base,#fff)!important;color:var(--dsw-alias-label-primary,#171717)!important;display:flex!important;flex-direction:column!important}
|
|
199
|
+
[data-dvw-browser-page]::backdrop{background:transparent;pointer-events:none}
|
|
200
|
+
[data-dvw-dsh-page]{position:fixed!important;z-index:106!important;margin:0!important;display:flex!important;flex-direction:column!important;box-sizing:border-box!important;overflow:hidden!important;background:var(--dsw-alias-bg-base,#fff)!important;border:0!important;border-radius:0!important;box-shadow:none!important;--dsh-frame-top-clearance:0px;--dsh-frame-chrome-top:0px}
|
|
201
|
+
[data-dvw-dsh-page][data-dvw-dsh-hidden]{display:none!important}
|
|
191
202
|
`
|
|
192
203
|
|
|
193
204
|
export function installStyles(): () => void {
|