dsh-better-sidebar 0.10.0
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/LICENSE +21 -0
- package/README.md +139 -0
- package/README_EN.md +139 -0
- package/lib/client-docx.js +7649 -0
- package/lib/client-editor.js +32677 -0
- package/lib/client-pptx.js +63438 -0
- package/lib/client-registry.js +8021 -0
- package/lib/client-terminal.js +8060 -0
- package/lib/client-xlsx.js +359755 -0
- package/lib/client.js +8021 -0
- package/lib/index.js +2604 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-pty.d.ts +220 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +13 -0
- package/lib/types/client/BrowserView.d.ts +22 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +10 -0
- package/lib/types/client/ExplorerView.d.ts +9 -0
- package/lib/types/client/GitView.d.ts +8 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/PptxView.d.ts +6 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +27 -0
- package/lib/types/client/Sidebar.d.ts +6 -0
- package/lib/types/client/SubagentView.d.ts +16 -0
- package/lib/types/client/TabBar.d.ts +38 -0
- package/lib/types/client/TerminalView.d.ts +8 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/api.d.ts +170 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +12 -0
- package/lib/types/client/browser.d.ts +50 -0
- package/lib/types/client/builtins/index.d.ts +17 -0
- package/lib/types/client/builtins/tabs.d.ts +6 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chunk-loader.d.ts +74 -0
- package/lib/types/client/chunks/docx.d.ts +8 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/pptx.d.ts +8 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/chunks/xlsx.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/docx-view.d.ts +8 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/icons.d.ts +47 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +10 -0
- package/lib/types/client/intercept.d.ts +20 -0
- package/lib/types/client/lang.d.ts +12 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +35 -0
- package/lib/types/client/locales.d.ts +237 -0
- package/lib/types/client/office-shared.d.ts +33 -0
- package/lib/types/client/office-types.d.ts +36 -0
- package/lib/types/client/office-view.d.ts +12 -0
- package/lib/types/client/openpath-intercept.d.ts +41 -0
- package/lib/types/client/paths.d.ts +19 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/prefs.d.ts +30 -0
- package/lib/types/client/produced-files.d.ts +27 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +214 -0
- package/lib/types/client/split-pane.d.ts +29 -0
- package/lib/types/client/state.d.ts +322 -0
- package/lib/types/client/subagent-activity.d.ts +35 -0
- package/lib/types/client/subagent-detect.d.ts +47 -0
- package/lib/types/client/subagent-jobs.d.ts +63 -0
- package/lib/types/client/theme.d.ts +28 -0
- package/lib/types/client/xlsx-to-univer.d.ts +33 -0
- package/lib/types/client/xlsx-view.d.ts +10 -0
- package/lib/types/config.d.ts +41 -0
- package/lib/types/context-types.d.ts +431 -0
- package/lib/types/fs-tree.d.ts +41 -0
- package/lib/types/git.d.ts +75 -0
- package/lib/types/html-route.d.ts +48 -0
- package/lib/types/index.d.ts +39 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/jobs-routes.d.ts +45 -0
- package/lib/types/prefs-shared.d.ts +106 -0
- package/lib/types/pty-manager.d.ts +76 -0
- package/lib/types/tools.d.ts +28 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +168 -0
- package/src/agent-pty.ts +519 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +130 -0
- package/src/client/BrowserView.tsx +253 -0
- package/src/client/DiffTab.tsx +110 -0
- package/src/client/DiffView.tsx +266 -0
- package/src/client/EditorHost.tsx +101 -0
- package/src/client/ExplorerView.tsx +283 -0
- package/src/client/GitView.tsx +540 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/PptxView.tsx +131 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +271 -0
- package/src/client/SideCardSection.tsx +426 -0
- package/src/client/Sidebar.tsx +861 -0
- package/src/client/SubagentView.module.css +547 -0
- package/src/client/SubagentView.tsx +866 -0
- package/src/client/TabBar.tsx +188 -0
- package/src/client/TerminalView.tsx +233 -0
- package/src/client/TextEditor.tsx +382 -0
- package/src/client/api.ts +215 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +51 -0
- package/src/client/browser.ts +119 -0
- package/src/client/builtins/index.ts +33 -0
- package/src/client/builtins/tabs.tsx +211 -0
- package/src/client/builtins/viewers.tsx +151 -0
- package/src/client/chunk-loader.ts +196 -0
- package/src/client/chunks/docx.tsx +8 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/pptx.tsx +8 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/chunks/xlsx.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/docx-view.tsx +113 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/icons.tsx +137 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +238 -0
- package/src/client/intercept.tsx +94 -0
- package/src/client/lang.ts +95 -0
- package/src/client/layout.css +54 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +66 -0
- package/src/client/locales.ts +476 -0
- package/src/client/office-shared.tsx +40 -0
- package/src/client/office-types.ts +48 -0
- package/src/client/office-view.tsx +12 -0
- package/src/client/openpath-intercept.ts +62 -0
- package/src/client/paths.ts +28 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/prefs.ts +100 -0
- package/src/client/produced-files.ts +87 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +434 -0
- package/src/client/sidebar.module.css +1917 -0
- package/src/client/split-pane.tsx +292 -0
- package/src/client/state.ts +1048 -0
- package/src/client/subagent-activity.ts +67 -0
- package/src/client/subagent-detect.ts +125 -0
- package/src/client/subagent-jobs.ts +161 -0
- package/src/client/theme.ts +44 -0
- package/src/client/xlsx-to-univer.ts +260 -0
- package/src/client/xlsx-view.tsx +116 -0
- package/src/config.ts +95 -0
- package/src/context-types.ts +445 -0
- package/src/fs-tree.ts +115 -0
- package/src/git.ts +236 -0
- package/src/html-route.ts +70 -0
- package/src/index.ts +897 -0
- package/src/invariant.ts +32 -0
- package/src/jobs-routes.ts +257 -0
- package/src/prefs-shared.ts +127 -0
- package/src/pty-manager.ts +199 -0
- package/src/tools.ts +477 -0
- package/src/trust-fence.ts +77 -0
- package/src/wire.ts +94 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icons the sidebar needs beyond the primitives set: a terminal glyph (the
|
|
3
|
+
* icon library has none), a diff glyph, and the two panel-toggle glyphs for
|
|
4
|
+
* the top-right cluster. Per-tab icons live on the tab descriptors
|
|
5
|
+
* (`descriptor.icon`), not in a type-keyed switch — the icon mapping was
|
|
6
|
+
* registry-ized with the tab types.
|
|
7
|
+
*/
|
|
8
|
+
import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Right-panel toggle glyph (the "侧拉" button): a frame with a filled strip
|
|
12
|
+
* along its RIGHT edge, in the app's outline style (1.5px stroke,
|
|
13
|
+
* currentColor).
|
|
14
|
+
*/
|
|
15
|
+
export const IconPanelRightOutline16 = ({ size = 16, className }: IconProps) => (
|
|
16
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
17
|
+
<rect x="1.5" y="2" width="13" height="12" rx="2.5" stroke="currentColor" strokeWidth="1.5" />
|
|
18
|
+
<rect x="10.5" y="3.25" width="2.75" height="9.5" rx="1" fill="currentColor" stroke="none" />
|
|
19
|
+
</svg>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Bottom-panel toggle glyph (the "底栏" button): a frame with a filled strip
|
|
24
|
+
* along its BOTTOM edge, in the app's outline style.
|
|
25
|
+
*/
|
|
26
|
+
export const IconPanelBottomOutline16 = ({ size = 16, className }: IconProps) => (
|
|
27
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
28
|
+
<rect x="1.5" y="2" width="13" height="12" rx="2.5" stroke="currentColor" strokeWidth="1.5" />
|
|
29
|
+
<rect x="3.25" y="10" width="9.5" height="2.75" rx="1" fill="currentColor" stroke="none" />
|
|
30
|
+
</svg>
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Terminal glyph in the app's outline style (1.5px stroke, currentColor):
|
|
35
|
+
* a rounded frame with a prompt chevron and underscore cursor.
|
|
36
|
+
*/
|
|
37
|
+
export const IconTerminalOutline16 = ({ size = 16, className }: IconProps) => (
|
|
38
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
39
|
+
<rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" strokeWidth="1.5" />
|
|
40
|
+
<path d="M4.5 6.25 6.75 8 4.5 9.75" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
41
|
+
<path d="M8.5 10.4h3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
42
|
+
</svg>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
/** Diff glyph in the app's outline style: a file frame with a plus and a minus row. */
|
|
46
|
+
export const IconDiffOutline16 = ({ size = 16, className }: IconProps) => (
|
|
47
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
48
|
+
<rect x="1.5" y="1.5" width="13" height="13" rx="2.5" stroke="currentColor" strokeWidth="1.5" />
|
|
49
|
+
<path d="M4 5h3M5.5 3.5v3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
50
|
+
<path d="M9.5 12.5h2.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
51
|
+
</svg>
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Stop glyph for the background-job kill button: a filled square in the
|
|
56
|
+
* app's outline scale (16), the universal "halt this work" mark.
|
|
57
|
+
*/
|
|
58
|
+
export const IconStopOutline16 = ({ size = 16, className }: IconProps) => (
|
|
59
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
60
|
+
<rect x="4" y="4" width="8" height="8" rx="1.5" fill="currentColor" stroke="none" />
|
|
61
|
+
</svg>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// ── File-viewer inventory glyphs (Side card settings page) ────────────────
|
|
65
|
+
|
|
66
|
+
/** Image viewer glyph: a picture frame with a sun and a mountain. */
|
|
67
|
+
export const IconImageOutline16 = ({ size = 16, className }: IconProps) => (
|
|
68
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
69
|
+
<rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" strokeWidth="1.5" />
|
|
70
|
+
<circle cx="5.5" cy="6" r="1.2" stroke="currentColor" strokeWidth="1.5" />
|
|
71
|
+
<path d="m3.5 12 3-3 2.25 2.25L11.5 8.5 13 10.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
72
|
+
</svg>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
/** PDF viewer glyph: a document frame with the "PDF" label. */
|
|
76
|
+
export const IconPdfOutline16 = ({ size = 16, className }: IconProps) => (
|
|
77
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
78
|
+
<path d="M3.5 1.5h6.5L13.5 5v9.5h-10z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
|
|
79
|
+
<path d="M9.5 1.5V5h4" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
|
|
80
|
+
<path d="M5 13.5v-3h1.4c.75 0 1.1.32 1.1.85 0 .54-.35.85-1.1.85H5.3" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
|
81
|
+
<path d="M8.3 13.5v-3h1.05c.8 0 1.35.5 1.35 1.5s-.55 1.5-1.35 1.5z" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
|
82
|
+
<path d="M11.6 13.5v-3h1.3" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" />
|
|
83
|
+
</svg>
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
/** Word viewer glyph: a document frame with a "W". */
|
|
87
|
+
export const IconDocxOutline16 = ({ size = 16, className }: IconProps) => (
|
|
88
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
89
|
+
<path d="M3.5 1.5h6.5L13.5 5v9.5h-10z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
|
|
90
|
+
<path d="M9.5 1.5V5h4" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
|
|
91
|
+
<path d="M6.2 13.4 7.4 10l1.2 3.4M7.4 10.6l-.35-1.1c-.2-.62.2-1.25.85-1.25h.2c.65 0 1.05.63.85 1.25l-.35 1.1" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
|
92
|
+
<path d="M8.75 10.6 9.2 9.4" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" />
|
|
93
|
+
</svg>
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
/** Excel viewer glyph: a spreadsheet grid. */
|
|
97
|
+
export const IconXlsxOutline16 = ({ size = 16, className }: IconProps) => (
|
|
98
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
99
|
+
<rect x="1.5" y="2" width="13" height="12" rx="2" stroke="currentColor" strokeWidth="1.5" />
|
|
100
|
+
<path d="M1.5 6h13M1.5 9.5h13M6 6v8M10.5 6v8" stroke="currentColor" strokeWidth="1.25" />
|
|
101
|
+
<path d="m3.8 13.2 2-3M5.8 13.2l-2-3" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" />
|
|
102
|
+
</svg>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
/** PowerPoint viewer glyph: a chart with rising bars. */
|
|
106
|
+
export const IconPptxOutline16 = ({ size = 16, className }: IconProps) => (
|
|
107
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
108
|
+
<rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" strokeWidth="1.5" />
|
|
109
|
+
<path d="M4 12.5v-3M7 12.5V7M10 12.5V4.5M13 12.5v-1.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
110
|
+
</svg>
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
/** Markdown viewer glyph: the classic "M with a down arrow" badge. */
|
|
114
|
+
export const IconMarkdownOutline16 = ({ size = 16, className }: IconProps) => (
|
|
115
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
116
|
+
<rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" strokeWidth="1.5" />
|
|
117
|
+
<path d="M4 10.5V5.5l2 2.5 2-2.5v5M9.5 10.5v-5l2 2.5 2-2.5v5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
118
|
+
</svg>
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
/** HTML viewer glyph: a document frame with a "‹/›" tag pair. */
|
|
122
|
+
export const IconHtmlOutline16 = ({ size = 16, className }: IconProps) => (
|
|
123
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
124
|
+
<path d="M3.5 1.5h6.5L13.5 5v9.5h-10z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
|
|
125
|
+
<path d="M9.5 1.5V5h4" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
|
|
126
|
+
<path d="M5.6 13.2 4.2 10l1.4-3.2M7.4 6.8 8.8 10l-1.4 3.2" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
|
127
|
+
</svg>
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
/** Browser tab glyph: a globe with meridians. */
|
|
131
|
+
export const IconGlobeOutline16 = ({ size = 16, className }: IconProps) => (
|
|
132
|
+
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
133
|
+
<circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1.5" />
|
|
134
|
+
<ellipse cx="8" cy="8" rx="2.8" ry="6.5" stroke="currentColor" strokeWidth="1.5" />
|
|
135
|
+
<path d="M1.5 8h13M8 1.5c-2.4 1.8-2.4 11.2 0 13M8 1.5c2.4 1.8 2.4 11.2 0 13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
136
|
+
</svg>
|
|
137
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Image extension dispatch, separate from React for unit testing. */
|
|
2
|
+
export const IMAGE_EXTENSIONS = [
|
|
3
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.ico', '.avif',
|
|
4
|
+
] as readonly string[]
|
|
5
|
+
|
|
6
|
+
export function isImageExt(ext: string): boolean {
|
|
7
|
+
return IMAGE_EXTENSIONS.includes(ext)
|
|
8
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IME-composition key guard.
|
|
3
|
+
*
|
|
4
|
+
* While a Chinese/Japanese/Korean input method is composing (the user is
|
|
5
|
+
* picking a candidate from the IME window), every pressed key BELONGS to the
|
|
6
|
+
* input method: arrows move the candidate highlight, Enter/Space confirm the
|
|
7
|
+
* composition, Escape cancels it. Page code must not process those keys —
|
|
8
|
+
* a component that does (a number stepper calling preventDefault() on
|
|
9
|
+
* ArrowUp/ArrowDown, a submit handler reacting to Enter, ...) silently
|
|
10
|
+
* breaks the IME: candidates stop responding, the composition gets torn
|
|
11
|
+
* apart, and only bare letters come out.
|
|
12
|
+
*
|
|
13
|
+
* This guard enforces that rule at the document boundary: a capture-phase
|
|
14
|
+
* keydown/keyup listener that stops the event from propagating further
|
|
15
|
+
* whenever a composition is in progress. Because it runs in the capture
|
|
16
|
+
* phase on `document` — the outermost node — it fires BEFORE React's
|
|
17
|
+
* delegated handlers (attached at the root container) and before any native
|
|
18
|
+
* target/bubble listener, so an inlined third-party component (e.g. the
|
|
19
|
+
* Univer office UI bundled into this plugin) can never intercept
|
|
20
|
+
* composition keys. The browser's native IME processing is untouched:
|
|
21
|
+
* stopPropagation only silences page JS, not the default action.
|
|
22
|
+
*
|
|
23
|
+
* The composition signal follows the DSH core convention (InputBar's IME
|
|
24
|
+
* guard, issue #535): `isComposing` for modern engines, keyCode 229 as the
|
|
25
|
+
* legacy signal engines emit without isComposing.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** The pure decision: is this keyboard event part of an IME composition? */
|
|
29
|
+
export function isImeComposition(event: { isComposing: boolean; keyCode: number }): boolean {
|
|
30
|
+
return event.isComposing || event.keyCode === 229
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Register the document-level capture guard. Returns the disposer
|
|
35
|
+
* (HMR-safe; call through `ctx.effect`).
|
|
36
|
+
*/
|
|
37
|
+
export function registerImeGuard(): () => void {
|
|
38
|
+
const onKey = (event: KeyboardEvent): void => {
|
|
39
|
+
if (isImeComposition(event)) event.stopPropagation()
|
|
40
|
+
}
|
|
41
|
+
document.addEventListener('keydown', onKey, true)
|
|
42
|
+
document.addEventListener('keyup', onKey, true)
|
|
43
|
+
return () => {
|
|
44
|
+
document.removeEventListener('keydown', onKey, true)
|
|
45
|
+
document.removeEventListener('keyup', onKey, true)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client half of dsh-better-sidebar: resolves the user's "Side card"
|
|
3
|
+
* preferences through the plugin's own fenced settings route, mounts the
|
|
4
|
+
* right sidebar portal (inside an error boundary so a rendering failure
|
|
5
|
+
* shows an error strip instead of a blank panel), registers the turn-tail
|
|
6
|
+
* interception, and contributes the Side card settings section to the DSH
|
|
7
|
+
* Settings shell. Requires the runtime's slots and sessions services; the
|
|
8
|
+
* bundle itself is a module-table consumer only (react + ui-primitives +
|
|
9
|
+
* xterm, all provided or inlined).
|
|
10
|
+
*/
|
|
11
|
+
import { Component, createElement, type ErrorInfo, type ReactNode } from 'react'
|
|
12
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
13
|
+
import type { Context } from '../context-types.ts'
|
|
14
|
+
import { createSidebarStore } from './state.ts'
|
|
15
|
+
import { createBetterSidebarService } from './service.ts'
|
|
16
|
+
import { resetChunks } from './chunk-loader.ts'
|
|
17
|
+
import { registerBuiltins } from './builtins/index.ts'
|
|
18
|
+
import { Sidebar } from './Sidebar.tsx'
|
|
19
|
+
import { registerOpenPathInterception, registerTurnTailInterception } from './intercept.tsx'
|
|
20
|
+
import { registerLinkInterception } from './link-intercept.ts'
|
|
21
|
+
import { registerImeGuard } from './ime-guard.ts'
|
|
22
|
+
import { loadPrefs } from './prefs.ts'
|
|
23
|
+
import { SideCardSection } from './SideCardSection.tsx'
|
|
24
|
+
import { api } from './api.ts'
|
|
25
|
+
import { LOCALE_NS, attachLocale, t, zh, en } from './locales.ts'
|
|
26
|
+
import css from './sidebar.module.css'
|
|
27
|
+
import './layout.css'
|
|
28
|
+
|
|
29
|
+
/** Services required before mounting (provided by the client runtime; the
|
|
30
|
+
* locale service backs the sidebar's copy — see locales.ts). */
|
|
31
|
+
export const inject = ['slots', 'sessions', 'connection', 'workspaces', 'locale']
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Error boundary over the sidebar tree: a render error must never blank the
|
|
35
|
+
* whole panel silently — it shows a dismissible error strip and logs the
|
|
36
|
+
* stack for diagnosis.
|
|
37
|
+
*/
|
|
38
|
+
class SidebarBoundary extends Component<{ children: ReactNode }, { error: string | null }> {
|
|
39
|
+
state = { error: null as string | null }
|
|
40
|
+
|
|
41
|
+
static getDerivedStateFromError(error: unknown): { error: string } {
|
|
42
|
+
return { error: error instanceof Error ? error.message : String(error) }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
46
|
+
console.error('[dsh-better-sidebar] render error:', error, info.componentStack)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
render(): ReactNode {
|
|
50
|
+
if (this.state.error !== null) {
|
|
51
|
+
return (
|
|
52
|
+
<div className={css.boundaryError}>
|
|
53
|
+
<span>dsh-better-sidebar: {this.state.error}</span>
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
className={css.terminalRetry}
|
|
57
|
+
onClick={() => { this.setState({ error: null }) }}
|
|
58
|
+
>
|
|
59
|
+
{t('terminalRetry')}
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
return this.props.children
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Client plugin body.
|
|
70
|
+
* @param ctx - the client cordis context (slots, sessions).
|
|
71
|
+
*/
|
|
72
|
+
export function apply(ctx: Context): void {
|
|
73
|
+
// The sidebar follows the DSH i18n system: attach the locale service so
|
|
74
|
+
// the module-level t()/isZh() resolve the Host-backed language preference
|
|
75
|
+
// (and switch live — the Sidebar root subscribes to it), and register the
|
|
76
|
+
// plugin's dictionaries into the shared locale registry. The disposers
|
|
77
|
+
// run on fiber disposal, so re-activation (HMR) re-registers cleanly.
|
|
78
|
+
attachLocale(ctx.locale)
|
|
79
|
+
ctx.effect(() => {
|
|
80
|
+
const offZh = ctx.locale.register(LOCALE_NS, 'zh', zh)
|
|
81
|
+
const offEn = ctx.locale.register(LOCALE_NS, 'en', en)
|
|
82
|
+
return () => { offZh(); offEn() }
|
|
83
|
+
}, 'dsh-better-sidebar: dictionaries')
|
|
84
|
+
// One store instance per activation: production code creates it only here,
|
|
85
|
+
// then hands it to the mounted panel and closes over it in the slot
|
|
86
|
+
// registrations (the official createXXXStore() factory rule — no
|
|
87
|
+
// module-level singleton).
|
|
88
|
+
const sidebarStore = createSidebarStore()
|
|
89
|
+
// The sidebar registry service: external plugins register tab types and
|
|
90
|
+
// file previewers through `ctx.betterSidebar.registerTab/registerFileViewer`.
|
|
91
|
+
// Published before the panel mounts so consumers injecting 'betterSidebar'
|
|
92
|
+
// are ready by the time the sidebar renders.
|
|
93
|
+
const service = createBetterSidebarService(sidebarStore)
|
|
94
|
+
ctx.provide('betterSidebar', service)
|
|
95
|
+
// Register the plugin's own built-in tabs and viewers through the same
|
|
96
|
+
// service (eating our own dogfood). The disposer unregisters them on
|
|
97
|
+
// fiber disposal (HMR-safe).
|
|
98
|
+
ctx.effect(
|
|
99
|
+
() => registerBuiltins(ctx, service),
|
|
100
|
+
'dsh-better-sidebar: register built-in tabs and viewers',
|
|
101
|
+
)
|
|
102
|
+
// A failure anywhere in the client lifecycle must never take the app down
|
|
103
|
+
// silently: log with the plugin prefix and pin a visible diagnostic strip
|
|
104
|
+
// to the page so a blank panel is never the only symptom.
|
|
105
|
+
const fail = (phase: string, error: unknown): void => {
|
|
106
|
+
console.error(`[dsh-better-sidebar] ${phase} error:`, error)
|
|
107
|
+
try {
|
|
108
|
+
const bar = document.createElement('div')
|
|
109
|
+
bar.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483000;max-width:70vw;padding:8px 12px;'
|
|
110
|
+
+ 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#f2a1a1;background:#1b1b22;'
|
|
111
|
+
+ 'border:1px solid #f2a1a1;border-radius:8px;white-space:pre-wrap'
|
|
112
|
+
bar.textContent = `[dsh-better-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
|
|
113
|
+
document.body.appendChild(bar)
|
|
114
|
+
} catch {
|
|
115
|
+
// Nothing left to report with.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
// Fresh chunk state for this activation: invalidate any chunk factories
|
|
120
|
+
// registered by a previous fiber (HMR) and drop the in-memory load cache
|
|
121
|
+
// so the next lazy open re-fetches the current chunk scripts.
|
|
122
|
+
resetChunks()
|
|
123
|
+
ctx.effect(() => {
|
|
124
|
+
let disposed = false
|
|
125
|
+
let root: Root | undefined
|
|
126
|
+
let host: HTMLDivElement | undefined
|
|
127
|
+
void (async () => {
|
|
128
|
+
// Resolve the user's side card prefs BEFORE the first session seeds,
|
|
129
|
+
// so a brand-new conversation opens (or stays closed) at the chosen
|
|
130
|
+
// width from first paint. A settings route failure falls back to the
|
|
131
|
+
// schema defaults; the sidebar still mounts (a stalled wire gives up
|
|
132
|
+
// after the timeout and mounts on the defaults).
|
|
133
|
+
const prefs = await Promise.race([
|
|
134
|
+
loadPrefs(api),
|
|
135
|
+
new Promise<null>(resolve => { const timer = window.setTimeout(() => resolve(null), 2000) }),
|
|
136
|
+
])
|
|
137
|
+
if (prefs !== null) sidebarStore.setPrefs(prefs)
|
|
138
|
+
if (disposed) return
|
|
139
|
+
try {
|
|
140
|
+
host = document.createElement('div')
|
|
141
|
+
host.setAttribute('data-dsh-better-sidebar', '')
|
|
142
|
+
document.body.appendChild(host)
|
|
143
|
+
root = createRoot(host)
|
|
144
|
+
root.render(createElement(SidebarBoundary, null, createElement(Sidebar, { ctx, store: sidebarStore })))
|
|
145
|
+
} catch (error) {
|
|
146
|
+
fail('mount', error)
|
|
147
|
+
}
|
|
148
|
+
})()
|
|
149
|
+
return () => {
|
|
150
|
+
disposed = true
|
|
151
|
+
root?.unmount()
|
|
152
|
+
host?.remove()
|
|
153
|
+
}
|
|
154
|
+
}, 'dsh-better-sidebar: sidebar mount')
|
|
155
|
+
|
|
156
|
+
ctx.effect(
|
|
157
|
+
() => {
|
|
158
|
+
try {
|
|
159
|
+
return registerTurnTailInterception(ctx, sidebarStore)
|
|
160
|
+
} catch (error) {
|
|
161
|
+
fail('interception', error)
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
'dsh-better-sidebar: turn-tail interception',
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
ctx.effect(
|
|
169
|
+
() => {
|
|
170
|
+
try {
|
|
171
|
+
return registerOpenPathInterception(ctx, sidebarStore)
|
|
172
|
+
} catch (error) {
|
|
173
|
+
fail('interception', error)
|
|
174
|
+
return undefined
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
'dsh-better-sidebar: open-path interception',
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
ctx.effect(
|
|
181
|
+
() => {
|
|
182
|
+
try {
|
|
183
|
+
// External http(s) links in the chat/GUI open the sidebar browser
|
|
184
|
+
// instead of a new window (gated on the browserInterceptLinks pref
|
|
185
|
+
// AND the browser tab's enable switch; Ctrl/Cmd+click bypasses).
|
|
186
|
+
return registerLinkInterception({
|
|
187
|
+
takeoverEnabled: () => sidebarStore.getPrefs().browserInterceptLinks !== false
|
|
188
|
+
&& sidebarStore.getPrefs().tabsEnabled['browser'] !== false,
|
|
189
|
+
openInSidebar: (url) => {
|
|
190
|
+
let title: string | undefined
|
|
191
|
+
try { title = new URL(url).hostname } catch { /* keep the default title */ }
|
|
192
|
+
ctx.betterSidebar?.openTab({ type: 'browser', url, title })
|
|
193
|
+
},
|
|
194
|
+
selfOrigin: window.location.origin,
|
|
195
|
+
})
|
|
196
|
+
} catch (error) {
|
|
197
|
+
fail('interception', error)
|
|
198
|
+
return undefined
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
'dsh-better-sidebar: link interception',
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
// The IME guard: composition keys (candidate arrows, confirm, cancel)
|
|
205
|
+
// belong to the input method, never to page JS. Inlined third-party UI
|
|
206
|
+
// (Univer's office controls) has shipped unguarded keydown handlers that
|
|
207
|
+
// hijack ArrowUp/ArrowDown and break Chinese input (#562 regression); the
|
|
208
|
+
// document-capture guard neutralizes the whole class before React or any
|
|
209
|
+
// native listener sees the event. Registered as early as possible so no
|
|
210
|
+
// other capture-phase listener can win the ordering race.
|
|
211
|
+
ctx.effect(
|
|
212
|
+
() => {
|
|
213
|
+
try {
|
|
214
|
+
return registerImeGuard()
|
|
215
|
+
} catch (error) {
|
|
216
|
+
fail('ime guard', error)
|
|
217
|
+
return undefined
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
'dsh-better-sidebar: IME composition guard',
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
// The "Side card" settings section: appears in the DSH Settings shell
|
|
224
|
+
// once the shell's declaration is on the ledger (slots.inject waits for
|
|
225
|
+
// it); the section reads/writes the prefs through the plugin's own
|
|
226
|
+
// fenced settings route, keeps the shared store in sync, and renders the
|
|
227
|
+
// declarative enable/disable inventory from the tab/viewer registry.
|
|
228
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
229
|
+
name: 'settings.section',
|
|
230
|
+
id: 'better-sidebar',
|
|
231
|
+
order: 100,
|
|
232
|
+
label: () => t('settingsNav'),
|
|
233
|
+
inject: () => ({ store: sidebarStore, service }),
|
|
234
|
+
}, SideCardSection))
|
|
235
|
+
} catch (error) {
|
|
236
|
+
fail('load', error)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interception of the chat's produced-files row: the turn-tail chain entry
|
|
3
|
+
* that replaces ui-deliverables' row when the closing turn produced files.
|
|
4
|
+
* The takeover looks identical (same chip row); the chips open the file in
|
|
5
|
+
* the sidebar instead of the host OS. Priority -1 runs before the default-0
|
|
6
|
+
* deliverables entry; when nothing was produced the selector returns null
|
|
7
|
+
* and the original row renders unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import { IconCodeOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
10
|
+
import type { Context } from '../context-types.ts'
|
|
11
|
+
import type { SidebarStore } from './state.ts'
|
|
12
|
+
import { t } from './locales.ts'
|
|
13
|
+
import { resolveSidebarPath, selectProducedFiles } from './produced-files.ts'
|
|
14
|
+
import { wrapOpenPath } from './openpath-intercept.ts'
|
|
15
|
+
import css from './sidebar.module.css'
|
|
16
|
+
|
|
17
|
+
/** Open a file in the sidebar's editor (used by the intercepted row and the explorer). */
|
|
18
|
+
export function openSidebarFile(ctx: Context, store: SidebarStore, sessionId: string, path: string): void {
|
|
19
|
+
const summary = ctx.sessions.list.getSnapshot().byId[sessionId]
|
|
20
|
+
const absolute = resolveSidebarPath(summary?.cwd, path)
|
|
21
|
+
const at = Math.max(absolute.lastIndexOf('/'), absolute.lastIndexOf('\\'))
|
|
22
|
+
const title = at === -1 ? absolute : absolute.slice(at + 1)
|
|
23
|
+
// Route through the sidebar service so the editor descriptor's dedupeKey
|
|
24
|
+
// (per-path) applies; the id is path-derived so multiple editors coexist.
|
|
25
|
+
ctx.betterSidebar?.openTab({ type: 'editor', title, path: absolute, id: `editor:${absolute}` })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The intercepted produced-files row (visual twin of the deliverables chips). */
|
|
29
|
+
export function SidebarProducedFiles(props: {
|
|
30
|
+
matched: readonly string[]
|
|
31
|
+
openInSidebar: (path: string) => void
|
|
32
|
+
}) {
|
|
33
|
+
const { matched, openInSidebar } = props
|
|
34
|
+
const shown = matched.slice(0, 6)
|
|
35
|
+
const hidden = matched.length - shown.length
|
|
36
|
+
return (
|
|
37
|
+
<div className={css.producedRow}>
|
|
38
|
+
<span className={css.producedLabel}>{t('produced')}</span>
|
|
39
|
+
{shown.map(path => {
|
|
40
|
+
const at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
|
41
|
+
const name = at === -1 ? path : path.slice(at + 1)
|
|
42
|
+
return (
|
|
43
|
+
<button
|
|
44
|
+
key={path}
|
|
45
|
+
type="button"
|
|
46
|
+
className={css.producedChip}
|
|
47
|
+
title={path}
|
|
48
|
+
onClick={() => { openInSidebar(path) }}
|
|
49
|
+
>
|
|
50
|
+
<IconCodeOutline16 size={12} />
|
|
51
|
+
<span>{name}</span>
|
|
52
|
+
</button>
|
|
53
|
+
)
|
|
54
|
+
})}
|
|
55
|
+
{hidden > 0 && <span className={css.producedMore}>+{hidden}</span>}
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Register the turn-tail interception (returns the disposer). */
|
|
61
|
+
export function registerTurnTailInterception(ctx: Context, store: SidebarStore): () => void {
|
|
62
|
+
return ctx.slots.register({
|
|
63
|
+
name: 'conversation.chat.turnTail',
|
|
64
|
+
// Decline the takeover while the editor tab type is disabled in the side
|
|
65
|
+
// card settings: the produced-files row falls back to the default
|
|
66
|
+
// deliverables behavior instead of offering chips that cannot open.
|
|
67
|
+
select: (owner) => {
|
|
68
|
+
if (store.getPrefs().tabsEnabled['editor'] === false) return null
|
|
69
|
+
return selectProducedFiles(owner)
|
|
70
|
+
},
|
|
71
|
+
priority: -1,
|
|
72
|
+
registrant: 'dsh-better-sidebar',
|
|
73
|
+
inject: (sessionId: string) => ({
|
|
74
|
+
openInSidebar: (path: string) => { openSidebarFile(ctx, store, sessionId, path) },
|
|
75
|
+
}),
|
|
76
|
+
}, SidebarProducedFiles)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Register the chat file-open interception: wraps `ctx.workspaces.openPath`
|
|
81
|
+
* — the single funnel every chat-side file open goes through (tool-row path
|
|
82
|
+
* links, the produced-files row, prose mentions) — so opens land in the
|
|
83
|
+
* sidebar editor instead of the Host OS. Gated by BOTH the `interceptOpenPath`
|
|
84
|
+
* pref and the editor tab's enable switch; declined opens fall through to
|
|
85
|
+
* the original method. Returns the disposer restoring the original (HMR-safe).
|
|
86
|
+
*/
|
|
87
|
+
export function registerOpenPathInterception(ctx: Context, store: SidebarStore): () => void {
|
|
88
|
+
return wrapOpenPath(ctx.workspaces, {
|
|
89
|
+
takeoverEnabled: () => store.getPrefs().interceptOpenPath !== false
|
|
90
|
+
&& store.getPrefs().tabsEnabled['editor'] !== false,
|
|
91
|
+
currentSessionId: () => ctx.sessions.list.getSnapshot().current,
|
|
92
|
+
openInSidebar: (path, sessionId) => { openSidebarFile(ctx, store, sessionId, path) },
|
|
93
|
+
})
|
|
94
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax highlighting for the file editor: extension → CodeMirror language
|
|
3
|
+
* mapping. The key derivation is pure and unit-tested; the factories pull in
|
|
4
|
+
* the CodeMirror language packages (bundled into the client).
|
|
5
|
+
*/
|
|
6
|
+
import { Language, LanguageSupport, StreamLanguage } from '@codemirror/language'
|
|
7
|
+
import { javascript } from '@codemirror/lang-javascript'
|
|
8
|
+
import { json } from '@codemirror/lang-json'
|
|
9
|
+
import { markdown } from '@codemirror/lang-markdown'
|
|
10
|
+
import { python } from '@codemirror/lang-python'
|
|
11
|
+
import { html } from '@codemirror/lang-html'
|
|
12
|
+
import { css } from '@codemirror/lang-css'
|
|
13
|
+
import { xml } from '@codemirror/lang-xml'
|
|
14
|
+
import { yaml } from '@codemirror/lang-yaml'
|
|
15
|
+
import { sql } from '@codemirror/lang-sql'
|
|
16
|
+
import { java } from '@codemirror/lang-java'
|
|
17
|
+
import { cpp } from '@codemirror/lang-cpp'
|
|
18
|
+
import { rust } from '@codemirror/lang-rust'
|
|
19
|
+
import { go } from '@codemirror/lang-go'
|
|
20
|
+
import { php } from '@codemirror/lang-php'
|
|
21
|
+
import { shell } from '@codemirror/legacy-modes/mode/shell'
|
|
22
|
+
import { toml } from '@codemirror/legacy-modes/mode/toml'
|
|
23
|
+
import { nginx } from '@codemirror/legacy-modes/mode/nginx'
|
|
24
|
+
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'
|
|
25
|
+
import { properties } from '@codemirror/legacy-modes/mode/properties'
|
|
26
|
+
|
|
27
|
+
/** The lowercased file extension of a path ('' when none). */
|
|
28
|
+
export function extOf(path: string): string {
|
|
29
|
+
const at = path.lastIndexOf('.')
|
|
30
|
+
if (at === -1) return ''
|
|
31
|
+
const base = path.slice(at + 1).toLowerCase()
|
|
32
|
+
return base.includes('/') || base.includes('\\') ? '' : base
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Language key for an extension, or null for plain text. Pure (tested). */
|
|
36
|
+
export function languageKeyForExt(ext: string): string | null {
|
|
37
|
+
switch (ext) {
|
|
38
|
+
case 'js': case 'mjs': case 'cjs': return 'js'
|
|
39
|
+
case 'jsx': return 'jsx'
|
|
40
|
+
case 'ts': case 'mts': case 'cts': return 'ts'
|
|
41
|
+
case 'tsx': return 'tsx'
|
|
42
|
+
case 'json': case 'jsonc': return 'json'
|
|
43
|
+
case 'md': case 'markdown': return 'md'
|
|
44
|
+
case 'py': case 'pyw': return 'python'
|
|
45
|
+
case 'html': case 'htm': return 'html'
|
|
46
|
+
case 'css': return 'css'
|
|
47
|
+
case 'xml': case 'xsl': return 'xml'
|
|
48
|
+
case 'yaml': case 'yml': return 'yaml'
|
|
49
|
+
case 'sql': return 'sql'
|
|
50
|
+
case 'java': return 'java'
|
|
51
|
+
case 'c': case 'h': return 'c'
|
|
52
|
+
case 'cc': case 'cpp': case 'cxx': case 'hpp': case 'hh': case 'hxx': return 'cpp'
|
|
53
|
+
case 'rs': return 'rust'
|
|
54
|
+
case 'go': return 'go'
|
|
55
|
+
case 'php': return 'php'
|
|
56
|
+
case 'sh': case 'bash': case 'zsh': return 'shell'
|
|
57
|
+
case 'toml': return 'toml'
|
|
58
|
+
case 'nginx': case 'conf': return 'nginx'
|
|
59
|
+
case 'dockerfile': case 'docker': return 'dockerfile'
|
|
60
|
+
case 'properties': case 'env': return 'properties'
|
|
61
|
+
default: return null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const FACTORIES: Record<string, () => Language | LanguageSupport> = {
|
|
66
|
+
js: () => javascript({ jsx: true }),
|
|
67
|
+
jsx: () => javascript({ jsx: true }),
|
|
68
|
+
ts: () => javascript({ typescript: true }),
|
|
69
|
+
tsx: () => javascript({ typescript: true, jsx: true }),
|
|
70
|
+
json: () => json(),
|
|
71
|
+
md: () => markdown(),
|
|
72
|
+
python: () => python(),
|
|
73
|
+
html: () => html(),
|
|
74
|
+
css: () => css(),
|
|
75
|
+
xml: () => xml(),
|
|
76
|
+
yaml: () => yaml(),
|
|
77
|
+
sql: () => sql(),
|
|
78
|
+
java: () => java(),
|
|
79
|
+
c: () => cpp(),
|
|
80
|
+
cpp: () => cpp(),
|
|
81
|
+
rust: () => rust(),
|
|
82
|
+
go: () => go(),
|
|
83
|
+
php: () => php(),
|
|
84
|
+
shell: () => StreamLanguage.define(shell),
|
|
85
|
+
toml: () => StreamLanguage.define(toml),
|
|
86
|
+
nginx: () => StreamLanguage.define(nginx),
|
|
87
|
+
dockerfile: () => StreamLanguage.define(dockerFile),
|
|
88
|
+
properties: () => StreamLanguage.define(properties),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The CodeMirror language support for a path, or null for plain text. */
|
|
92
|
+
export function languageForPath(path: string): Language | LanguageSupport | null {
|
|
93
|
+
const key = languageKeyForExt(extOf(path))
|
|
94
|
+
return key === null ? null : FACTORIES[key]!()
|
|
95
|
+
}
|