dsh-vscode-workbench 0.1.2 → 0.1.3
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 +3 -1
- package/README.md +3 -1
- package/lib/client.js +68 -28
- package/lib/client.js.map +1 -1
- package/lib/types/client/Workbench.d.ts +9 -3
- package/lib/types/client/shortcuts.d.ts +3 -0
- package/package.json +1 -1
- package/src/client/Workbench.tsx +34 -17
- package/src/client/index.tsx +2 -0
- package/src/client/shortcuts.ts +17 -0
|
@@ -15,14 +15,20 @@ interface Tab extends FileDocument {
|
|
|
15
15
|
}
|
|
16
16
|
/** Return the basename shown in a tab while preserving its full internal path. */
|
|
17
17
|
export declare function tabLabel(tab: Pick<Tab, 'path' | 'title' | 'diffSourcePath'>): string;
|
|
18
|
-
interface
|
|
18
|
+
export interface GraphTrack {
|
|
19
|
+
hash: string;
|
|
20
|
+
color: number;
|
|
21
|
+
}
|
|
22
|
+
export interface GraphRow {
|
|
19
23
|
commit: GitCommit;
|
|
20
24
|
lane: number;
|
|
21
|
-
before:
|
|
22
|
-
after:
|
|
25
|
+
before: GraphTrack[];
|
|
26
|
+
after: GraphTrack[];
|
|
23
27
|
parentLanes: number[];
|
|
24
28
|
}
|
|
25
29
|
export declare function graphRows(commits: GitCommit[]): GraphRow[];
|
|
30
|
+
/** Draw a fixed-radius, orthogonal transition between two graph lanes. */
|
|
31
|
+
export declare function graphTrackPath(from: number, to: number, startY: number, endY: number, bendY: number): string;
|
|
26
32
|
export declare function Launcher({ wide, service }: {
|
|
27
33
|
wide: boolean;
|
|
28
34
|
service: VscodeWorkbench;
|
package/package.json
CHANGED
package/src/client/Workbench.tsx
CHANGED
|
@@ -74,38 +74,55 @@ function Search(props: { sessionId: string; onOpen(path: string, line: number, c
|
|
|
74
74
|
return <><div className="dvw-side-head">搜索</div><form className="dvw-search-form" onSubmit={event => { event.preventDefault(); void search() }}><input className="dvw-input" value={query} onChange={e => setQuery(e.target.value)} placeholder="搜索" autoFocus/><div className="dvw-options">{([['caseSensitive','Aa'],['wholeWord','Ab'],['regex','.*']] as const).map(([key,label]) => <label key={key}><input type="checkbox" checked={flags[key]} onChange={e => setFlags({ ...flags, [key]: e.target.checked })}/>{label}</label>)}<button className="dvw-icon" title="搜索" disabled={busy}><Icon name="search"/></button></div><input className="dvw-input" value={include} onChange={e=>setInclude(e.target.value)} placeholder="包含文件,例如 src/**"/><input className="dvw-input" value={exclude} onChange={e=>setExclude(e.target.value)} placeholder="排除文件,例如 **/*.test.ts"/></form>{error&&<div className="dvw-error">{error}</div>}{limited&&<div className="dvw-message">结果已截断到 2000 条</div>}<div className="dvw-results">{[...groups].map(([path, rows]) => <div key={path}><div className="dvw-result-file"><Icon name="chevron-down"/> {path} <span className="dvw-badge">{rows.length}</span></div>{rows.map((row,index)=><div className="dvw-result" key={`${row.line}:${row.column}:${index}`} onClick={()=>props.onOpen(path,row.line,row.column)}><span className="dvw-line">{row.line}</span><span className="dvw-preview">{row.preview}</span></div>)}</div>)}</div></>
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
interface
|
|
77
|
+
export interface GraphTrack { hash: string; color: number }
|
|
78
|
+
export interface GraphRow { commit: GitCommit; lane: number; before: GraphTrack[]; after: GraphTrack[]; parentLanes: number[] }
|
|
78
79
|
export function graphRows(commits: GitCommit[]): GraphRow[] {
|
|
79
|
-
const visible=new Set(commits.map(commit=>commit.hash))
|
|
80
|
-
let
|
|
80
|
+
const visible = new Set(commits.map(commit => commit.hash))
|
|
81
|
+
let nextColor = 0
|
|
82
|
+
let lanes: GraphTrack[] = commits[0] ? [{ hash: commits[0].hash, color: nextColor++ }] : []
|
|
81
83
|
return commits.map(commit => {
|
|
82
|
-
let lane = lanes.
|
|
83
|
-
if (lane < 0) { lanes=[commit.hash
|
|
84
|
-
const before=[...lanes]
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
let lane = lanes.findIndex(track => track.hash === commit.hash)
|
|
85
|
+
if (lane < 0) { lanes = [{ hash: commit.hash, color: nextColor++ }, ...lanes]; lane = 0 }
|
|
86
|
+
const before = [...lanes]
|
|
87
|
+
const current = before[lane]!
|
|
88
|
+
const after = before.filter(track => track.hash !== commit.hash)
|
|
89
|
+
const parents = commit.parents.filter(parent => visible.has(parent))
|
|
90
|
+
parents.forEach((parent, index) => {
|
|
91
|
+
if (after.some(track => track.hash === parent)) return
|
|
92
|
+
const color = index === 0 ? current.color : nextColor++
|
|
93
|
+
after.splice(Math.min(lane + index, after.length), 0, { hash: parent, color })
|
|
94
|
+
})
|
|
95
|
+
const parentLanes = parents.map(parent => after.findIndex(track => track.hash === parent)).filter(index => index >= 0)
|
|
96
|
+
lanes = after
|
|
97
|
+
return { commit, lane, before, after, parentLanes }
|
|
89
98
|
})
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
const GRAPH_COLORS = ['#4c8dff','#d94f9f','#4fc1b0','#d9a441','#9b7bea']
|
|
93
|
-
function graphColor(
|
|
102
|
+
function graphColor(color:number):string{return GRAPH_COLORS[color%GRAPH_COLORS.length]!}
|
|
94
103
|
function graphWidth(row:GraphRow):number{return Math.max(row.before.length,row.after.length,row.lane+1,1)*14+8}
|
|
95
104
|
|
|
105
|
+
/** Draw a fixed-radius, orthogonal transition between two graph lanes. */
|
|
106
|
+
export function graphTrackPath(from: number, to: number, startY: number, endY: number, bendY: number): string {
|
|
107
|
+
if (from === to) return `M ${from} ${startY} V ${endY}`
|
|
108
|
+
const direction = Math.sign(to - from)
|
|
109
|
+
const radius = Math.min(5, Math.abs(to - from) / 2, Math.abs(bendY - startY), Math.abs(endY - bendY))
|
|
110
|
+
return `M ${from} ${startY} V ${bendY - radius} Q ${from} ${bendY} ${from + direction * radius} ${bendY} H ${to - direction * radius} Q ${to} ${bendY} ${to} ${bendY + radius} V ${endY}`
|
|
111
|
+
}
|
|
112
|
+
|
|
96
113
|
function GraphTracks({row}:{row:GraphRow}) {
|
|
97
|
-
const width=graphWidth(row);const x=row.lane*14+8
|
|
114
|
+
const width=graphWidth(row);const x=row.lane*14+8;const current=row.before[row.lane]!
|
|
98
115
|
return <svg className="dvw-graph-svg" width={width} height="38" viewBox={`0 0 ${width} 38`} aria-hidden="true">
|
|
99
|
-
{row.before.map((
|
|
100
|
-
<path d={`M ${x} 0 V 19`} fill="none" stroke={graphColor(
|
|
101
|
-
{row.parentLanes.map((parent,index)=>{const target=parent*14+8;return <path key={`parent-${
|
|
102
|
-
<circle cx={x} cy="19" r="5" fill="var(--dvw-surface-1)" stroke={graphColor(
|
|
116
|
+
{row.before.map((track,lane)=>{if(track.hash===row.commit.hash)return null;const target=row.after.findIndex(next=>next.hash===track.hash);const nextLane=target>=0?target:lane;return <path key={`continue-${track.hash}`} d={graphTrackPath(lane*14+8,nextLane*14+8,0,38,19)} fill="none" stroke={graphColor(track.color)} strokeWidth="2" vectorEffect="non-scaling-stroke"/>})}
|
|
117
|
+
<path d={`M ${x} 0 V 19`} fill="none" stroke={graphColor(current.color)} strokeWidth="2" vectorEffect="non-scaling-stroke"/>
|
|
118
|
+
{row.parentLanes.map((parent,index)=>{const target=parent*14+8;const track=row.after[parent]!;return <path key={`parent-${track.hash}-${index}`} d={graphTrackPath(x,target,19,38,29)} fill="none" stroke={graphColor(track.color)} strokeWidth="2" vectorEffect="non-scaling-stroke"/>})}
|
|
119
|
+
<circle cx={x} cy="19" r="5" fill="var(--dvw-surface-1)" stroke={graphColor(current.color)} strokeWidth="2" vectorEffect="non-scaling-stroke"/>
|
|
103
120
|
</svg>
|
|
104
121
|
}
|
|
105
122
|
|
|
106
123
|
function GraphContinuation({row}:{row:GraphRow}) {
|
|
107
124
|
const width=graphWidth(row)
|
|
108
|
-
return <svg className="dvw-graph-continuation" width={width} height="100%" viewBox={`0 0 ${width} 100`} preserveAspectRatio="none" aria-hidden="true">{row.after.map((
|
|
125
|
+
return <svg className="dvw-graph-continuation" width={width} height="100%" viewBox={`0 0 ${width} 100`} preserveAspectRatio="none" aria-hidden="true">{row.after.map((track,lane)=><line key={track.hash} x1={lane*14+8} y1="0" x2={lane*14+8} y2="100" stroke={graphColor(track.color)} strokeWidth="2" vectorEffect="non-scaling-stroke"/>)}</svg>
|
|
109
126
|
}
|
|
110
127
|
|
|
111
128
|
function CommitGraph({ commits, expanded, files, onToggle, onOpenFile }: { commits: GitCommit[]; expanded?: string; files: Map<string,GitCommitFile[]>; onToggle(commit:GitCommit):void; onOpenFile(commit:GitCommit,file:GitCommitFile):void }) {
|
package/src/client/index.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import type { VscodeWorkbench } from './service.ts'
|
|
|
8
8
|
import { WorkbenchController } from './service.ts'
|
|
9
9
|
import { Launcher, Workbench } from './Workbench.tsx'
|
|
10
10
|
import { installStyles } from './styles.ts'
|
|
11
|
+
import { handleTerminalShortcut } from './shortcuts.ts'
|
|
11
12
|
|
|
12
13
|
export type { BottomPanelContribution, BottomPanelProps, VscodeWorkbench, WorkbenchCommand, WorkbenchSnapshot, WorkbenchView } from './service.ts'
|
|
13
14
|
|
|
@@ -33,6 +34,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
33
34
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dsh-vscode-workbench-overlay', inject: () => ({ service }) }, Overlay))
|
|
34
35
|
ctx.effect(() => {
|
|
35
36
|
const onKey = (event: KeyboardEvent) => {
|
|
37
|
+
if (handleTerminalShortcut(event, service)) return
|
|
36
38
|
if (event.isComposing || !event.ctrlKey || event.altKey) return
|
|
37
39
|
const view = event.shiftKey && event.code === 'KeyE' ? 'explorer' : event.shiftKey && event.code === 'KeyF' ? 'search' : event.shiftKey && event.code === 'KeyG' ? 'git' : undefined
|
|
38
40
|
if (view !== undefined) { event.preventDefault(); service.show(view) }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { VscodeWorkbench } from './service.ts'
|
|
2
|
+
|
|
3
|
+
/** Handle terminal shortcuts through contributions registered by a terminal adapter. */
|
|
4
|
+
export function handleTerminalShortcut(event: KeyboardEvent, workbench: VscodeWorkbench): boolean {
|
|
5
|
+
if (event.isComposing || !event.ctrlKey || event.altKey || event.code !== 'Backquote') return false
|
|
6
|
+
if (!workbench.getPanels().some(panel => panel.id === 'terminal')) return false
|
|
7
|
+
event.preventDefault()
|
|
8
|
+
event.stopImmediatePropagation()
|
|
9
|
+
workbench.show()
|
|
10
|
+
if (event.shiftKey) {
|
|
11
|
+
if (workbench.getSnapshot().bottomPanel !== 'terminal') workbench.toggleBottomPanel('terminal')
|
|
12
|
+
queueMicrotask(() => workbench.runCommand('terminal.new'))
|
|
13
|
+
} else {
|
|
14
|
+
workbench.toggleBottomPanel('terminal')
|
|
15
|
+
}
|
|
16
|
+
return true
|
|
17
|
+
}
|