@chatcode/cco-better-sidebar 0.18.0-alpha.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 +554 -0
- package/README_EN.md +563 -0
- package/assets/logo.svg +1 -0
- package/cordis.patch.yml +79 -0
- package/lib/client-editor.js +55744 -0
- package/lib/client-mermaid.js +208256 -0
- package/lib/client-registry.js +30378 -0
- package/lib/client-terminal.js +16894 -0
- package/lib/client.js +30378 -0
- package/lib/index.js +6897 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-opens.d.ts +65 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/chatcode-update.d.ts +47 -0
- package/lib/types/client/AccountMenu.d.ts +1 -0
- package/lib/types/client/AiRatioSection.d.ts +3 -0
- package/lib/types/client/BrowserView.d.ts +39 -0
- package/lib/types/client/ChatcodeGate.d.ts +6 -0
- package/lib/types/client/CodingPlanModelsSettings.d.ts +9 -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 +13 -0
- package/lib/types/client/FenceErrorNotice.d.ts +5 -0
- package/lib/types/client/FileTree.d.ts +41 -0
- package/lib/types/client/FreeWindow.d.ts +29 -0
- package/lib/types/client/GitView.d.ts +12 -0
- package/lib/types/client/MarkdownHtml.d.ts +32 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/ReleaseNotesDialog.d.ts +11 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/Sidebar.d.ts +8 -0
- package/lib/types/client/TabBar.d.ts +54 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +27 -0
- package/lib/types/client/UpdateDialog.d.ts +10 -0
- package/lib/types/client/UpdateSettingsPanel.d.ts +1 -0
- package/lib/types/client/UpdateSettingsSection.d.ts +10 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/UsageHeatmapSection.d.ts +4 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +345 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +17 -0
- package/lib/types/client/browser.d.ts +60 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chat-presentation.d.ts +13 -0
- package/lib/types/client/chunk-loader.d.ts +124 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.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/desktop-env.d.ts +35 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +66 -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 +19 -0
- package/lib/types/client/intercept.d.ts +44 -0
- package/lib/types/client/lang.d.ts +14 -0
- package/lib/types/client/layout-push.d.ts +15 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales-ar.d.ts +18 -0
- package/lib/types/client/locales-de.d.ts +4 -0
- package/lib/types/client/locales-fr.d.ts +10 -0
- package/lib/types/client/locales-hi.d.ts +17 -0
- package/lib/types/client/locales-id.d.ts +15 -0
- package/lib/types/client/locales-it.d.ts +8 -0
- package/lib/types/client/locales-ja.d.ts +17 -0
- package/lib/types/client/locales-ko.d.ts +9 -0
- package/lib/types/client/locales-nl.d.ts +16 -0
- package/lib/types/client/locales-pl.d.ts +19 -0
- package/lib/types/client/locales-pt.d.ts +1 -0
- package/lib/types/client/locales-ru.d.ts +13 -0
- package/lib/types/client/locales-sv.d.ts +1 -0
- package/lib/types/client/locales-th.d.ts +17 -0
- package/lib/types/client/locales-tr.d.ts +17 -0
- package/lib/types/client/locales-vi.d.ts +17 -0
- package/lib/types/client/locales-zh-HK.d.ts +32 -0
- package/lib/types/client/locales-zh-MO.d.ts +32 -0
- package/lib/types/client/locales-zh-TW.d.ts +32 -0
- package/lib/types/client/locales.d.ts +571 -0
- package/lib/types/client/markdown-html.d.ts +96 -0
- package/lib/types/client/markdown-images.d.ts +45 -0
- package/lib/types/client/markdown-labels.d.ts +21 -0
- package/lib/types/client/md-toc.d.ts +4 -0
- package/lib/types/client/mermaid-blocks.d.ts +47 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +52 -0
- package/lib/types/client/paths.d.ts +43 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/pinned.d.ts +86 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +29 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +445 -0
- package/lib/types/client/session-pins.d.ts +14 -0
- package/lib/types/client/settings-nav-icon.d.ts +27 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/split-pane.d.ts +38 -0
- package/lib/types/client/state.d.ts +480 -0
- package/lib/types/client/tab-content-memo.d.ts +40 -0
- package/lib/types/client/terminal-font.d.ts +78 -0
- package/lib/types/client/terminal-links.d.ts +119 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/ui-tweaks/appicons.d.ts +16 -0
- package/lib/types/client/ui-tweaks/archive.d.ts +42 -0
- package/lib/types/client/ui-tweaks/branding.d.ts +45 -0
- package/lib/types/client/ui-tweaks/cachehit.d.ts +41 -0
- package/lib/types/client/ui-tweaks/gitbar.d.ts +103 -0
- package/lib/types/client/ui-tweaks/index.d.ts +279 -0
- package/lib/types/client/ui-tweaks/mcp.d.ts +28 -0
- package/lib/types/client/ui-tweaks/notifier.d.ts +125 -0
- package/lib/types/client/ui-tweaks/whale.d.ts +55 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/useChatcodeAccount.d.ts +10 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/codingplan.d.ts +37 -0
- package/lib/types/config.d.ts +95 -0
- package/lib/types/context-types.d.ts +540 -0
- package/lib/types/fs-operations.d.ts +30 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +124 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/login-route.d.ts +2 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/path-security.d.ts +26 -0
- package/lib/types/prefs-shared.d.ts +257 -0
- package/lib/types/pty-deps.d.ts +79 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/tools.d.ts +34 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/ui-tweaks/archive.d.ts +71 -0
- package/lib/types/ui-tweaks/config.d.ts +186 -0
- package/lib/types/ui-tweaks/git-web.d.ts +43 -0
- package/lib/types/ui-tweaks/git.d.ts +311 -0
- package/lib/types/ui-tweaks/index.d.ts +17 -0
- package/lib/types/ui-tweaks/mcp.d.ts +103 -0
- package/lib/types/ui-tweaks/turn-outcome.d.ts +57 -0
- package/lib/types/ui-tweaks/web.d.ts +55 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +213 -0
- package/scripts/install.ps1 +310 -0
- package/scripts/install.sh +287 -0
- package/src/agent-opens.ts +265 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/chatcode-update.ts +193 -0
- package/src/client/AccountMenu.tsx +177 -0
- package/src/client/AiRatioSection.module.css +329 -0
- package/src/client/AiRatioSection.tsx +292 -0
- package/src/client/BrowserView.tsx +289 -0
- package/src/client/ChatcodeGate.tsx +35 -0
- package/src/client/CodingPlanModelsSettings.module.css +118 -0
- package/src/client/CodingPlanModelsSettings.tsx +101 -0
- package/src/client/DiffTab.tsx +114 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +563 -0
- package/src/client/FenceErrorNotice.tsx +40 -0
- package/src/client/FileTree.tsx +701 -0
- package/src/client/FreeWindow.tsx +337 -0
- package/src/client/GitView.tsx +722 -0
- package/src/client/MarkdownHtml.tsx +296 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/ReleaseNotesDialog.tsx +91 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +932 -0
- package/src/client/SideCardSection.tsx +1169 -0
- package/src/client/Sidebar.tsx +1857 -0
- package/src/client/TabBar.tsx +365 -0
- package/src/client/TerminalView.tsx +424 -0
- package/src/client/TextEditor.tsx +468 -0
- package/src/client/TreePanel.tsx +279 -0
- package/src/client/UpdateDialog.tsx +186 -0
- package/src/client/UpdateSettingsPanel.tsx +29 -0
- package/src/client/UpdateSettingsSection.tsx +22 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/UsageHeatmapSection.module.css +440 -0
- package/src/client/UsageHeatmapSection.tsx +301 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +433 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +61 -0
- package/src/client/browser.ts +167 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +275 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chat-presentation.ts +95 -0
- package/src/client/chunk-loader.ts +350 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.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/desktop-env.ts +72 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +170 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +501 -0
- package/src/client/intercept.tsx +172 -0
- package/src/client/lang.ts +225 -0
- package/src/client/layout-push.ts +37 -0
- package/src/client/layout.css +156 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales-ar.ts +387 -0
- package/src/client/locales-de.ts +372 -0
- package/src/client/locales-fr.ts +379 -0
- package/src/client/locales-hi.ts +386 -0
- package/src/client/locales-id.ts +384 -0
- package/src/client/locales-it.ts +377 -0
- package/src/client/locales-ja.ts +486 -0
- package/src/client/locales-ko.ts +378 -0
- package/src/client/locales-nl.ts +384 -0
- package/src/client/locales-pl.ts +388 -0
- package/src/client/locales-pt.ts +369 -0
- package/src/client/locales-ru.ts +384 -0
- package/src/client/locales-sv.ts +369 -0
- package/src/client/locales-th.ts +386 -0
- package/src/client/locales-tr.ts +386 -0
- package/src/client/locales-vi.ts +386 -0
- package/src/client/locales-zh-HK.ts +401 -0
- package/src/client/locales-zh-MO.ts +401 -0
- package/src/client/locales-zh-TW.ts +401 -0
- package/src/client/locales.ts +1229 -0
- package/src/client/markdown-html.ts +331 -0
- package/src/client/markdown-images.ts +137 -0
- package/src/client/markdown-labels.tsx +31 -0
- package/src/client/md-toc.tsx +127 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +406 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +79 -0
- package/src/client/paths.ts +63 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/pinned.ts +184 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +153 -0
- package/src/client/plugins-viewers.ts +44 -0
- package/src/client/prefs.ts +216 -0
- package/src/client/produced-files.ts +120 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +873 -0
- package/src/client/session-pins.tsx +334 -0
- package/src/client/settings-nav-icon.ts +55 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +3607 -0
- package/src/client/split-pane.tsx +333 -0
- package/src/client/state.ts +1646 -0
- package/src/client/tab-content-memo.ts +55 -0
- package/src/client/terminal-font.ts +224 -0
- package/src/client/terminal-links.ts +194 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/ui-tweaks/appicons.ts +24 -0
- package/src/client/ui-tweaks/archive.tsx +281 -0
- package/src/client/ui-tweaks/branding.tsx +232 -0
- package/src/client/ui-tweaks/cachehit.tsx +122 -0
- package/src/client/ui-tweaks/gitbar.tsx +2559 -0
- package/src/client/ui-tweaks/index.tsx +1696 -0
- package/src/client/ui-tweaks/mcp.tsx +582 -0
- package/src/client/ui-tweaks/notifier.ts +514 -0
- package/src/client/ui-tweaks/whale.tsx +171 -0
- package/src/client/upload.ts +187 -0
- package/src/client/useChatcodeAccount.ts +103 -0
- package/src/client/wco.ts +120 -0
- package/src/codingplan.ts +146 -0
- package/src/config.ts +207 -0
- package/src/context-types.ts +559 -0
- package/src/fs-operations.ts +97 -0
- package/src/fs-search.ts +113 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +446 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1524 -0
- package/src/invariant.ts +32 -0
- package/src/login-route.ts +26 -0
- package/src/open-external.ts +90 -0
- package/src/path-security.ts +81 -0
- package/src/prefs-shared.ts +303 -0
- package/src/pty-deps.ts +241 -0
- package/src/pty-manager.ts +376 -0
- package/src/tools.ts +482 -0
- package/src/trust-fence.ts +84 -0
- package/src/ui-tweaks/archive.ts +293 -0
- package/src/ui-tweaks/config.ts +257 -0
- package/src/ui-tweaks/git-web.ts +421 -0
- package/src/ui-tweaks/git.ts +1524 -0
- package/src/ui-tweaks/index.ts +61 -0
- package/src/ui-tweaks/mcp.ts +535 -0
- package/src/ui-tweaks/turn-outcome.ts +105 -0
- package/src/ui-tweaks/web.ts +225 -0
- package/src/wire.ts +110 -0
- package/vendor/addon-fit.js +2 -0
- package/vendor/xterm.css +218 -0
- package/vendor/xterm.js +5 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memo comparator for one tab's content cell (TabContent in Sidebar.tsx).
|
|
3
|
+
*
|
|
4
|
+
* Kept as a pure module so the "what re-renders a tab cell" contract is unit
|
|
5
|
+
* testable: geometry/store re-renders of the Sidebar shell must NOT reconcile
|
|
6
|
+
* every mounted tab (xterm/CodeMirror/FileTree subtrees — issue #315), while
|
|
7
|
+
* locale switches (localeRevision), tab-registry updates (tabsVersion), and
|
|
8
|
+
* a tab MOVING BETWEEN PANES (paneId — onOpenDiff closes over the pane id,
|
|
9
|
+
* and moveTab reuses the SAME tab object, so only the pane id identifies the
|
|
10
|
+
* move) must re-render the cell.
|
|
11
|
+
*/
|
|
12
|
+
import type { SidebarTab } from './state.ts'
|
|
13
|
+
|
|
14
|
+
/** The fields whose change invalidates a tab cell's memo. Anything a cell's
|
|
15
|
+
* render (or its callbacks) depends on must be listed here. */
|
|
16
|
+
export interface TabContentMemoKey {
|
|
17
|
+
tab: SidebarTab
|
|
18
|
+
/** The pane this tab currently lives in. onOpenDiff closes over it, and
|
|
19
|
+
* moveTab reuses the same tab object when the tab moves panes — without
|
|
20
|
+
* comparing paneId the cell would keep the source pane's callback. */
|
|
21
|
+
paneId: string
|
|
22
|
+
sessionId: string
|
|
23
|
+
cwd: string | undefined
|
|
24
|
+
visible: boolean
|
|
25
|
+
expanded: string[]
|
|
26
|
+
/** Files highlighted in the file tree (the "Show in folder" reveal). */
|
|
27
|
+
revealed: string[]
|
|
28
|
+
localeRevision: string
|
|
29
|
+
tabsVersion: number
|
|
30
|
+
/** When the tab is a pinned virtual tab (injected from another session),
|
|
31
|
+
* this overrides `tab.id` passed to the tab descriptor's component so
|
|
32
|
+
* TerminalView connects to the home session's PTY by the original id.
|
|
33
|
+
* Undefined for regular tabs (no override). */
|
|
34
|
+
effectiveTabId: string | undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** True when the cell may skip a re-render (all render-affecting fields
|
|
38
|
+
* unchanged). Callback/context identities are deliberately ignored: their
|
|
39
|
+
* captured dependencies are stable or covered by the compared fields
|
|
40
|
+
* (onReferenceFile → sessionId/cwd, onSubagentJump/onToggleDir → stable
|
|
41
|
+
* refs/closures, onOpenDiff → paneId). */
|
|
42
|
+
export function tabContentCompare(prev: TabContentMemoKey, next: TabContentMemoKey): boolean {
|
|
43
|
+
return (
|
|
44
|
+
prev.tab === next.tab &&
|
|
45
|
+
prev.paneId === next.paneId &&
|
|
46
|
+
prev.sessionId === next.sessionId &&
|
|
47
|
+
prev.cwd === next.cwd &&
|
|
48
|
+
prev.visible === next.visible &&
|
|
49
|
+
prev.expanded === next.expanded &&
|
|
50
|
+
prev.revealed === next.revealed &&
|
|
51
|
+
prev.localeRevision === next.localeRevision &&
|
|
52
|
+
prev.tabsVersion === next.tabsVersion &&
|
|
53
|
+
prev.effectiveTabId === next.effectiveTabId
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal font resolution: the user's custom font prefs (SidebarPrefs,
|
|
3
|
+
* configured under the terminal card's secondary settings) turned into the
|
|
4
|
+
* xterm options. Kept as a pure module (no DOM, no xterm) so the fallback
|
|
5
|
+
* chain and clamping are unit-testable without mounting a terminal.
|
|
6
|
+
*/
|
|
7
|
+
import { clampTerminalFontSize, type SidebarPrefs } from '../prefs-shared.ts'
|
|
8
|
+
|
|
9
|
+
/** The built-in fallback stack when neither the user nor the theme sets one. */
|
|
10
|
+
export const DEFAULT_TERMINAL_FONT_FAMILY = '"SF Mono", Menlo, Consolas, "Liberation Mono", monospace'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Icon fonts appended to whichever base stack wins, so shell prompts that
|
|
14
|
+
* draw glyphs from the Nerd Font Private Use Areas resolve to a real glyph
|
|
15
|
+
* instead of the missing-glyph box (aka tofu).
|
|
16
|
+
*
|
|
17
|
+
* Why this is needed even though the OS "should" fall back automatically:
|
|
18
|
+
* prompt frameworks (starship, powerlevel10k, oh-my-posh) take their icons
|
|
19
|
+
* from the PUA. Chromium's implicit system fallback reliably covers the
|
|
20
|
+
* *BMP* PUA (U+E000–U+F8FF — e.g. the U+E0B0 powerline separator) but NOT
|
|
21
|
+
* the *supplementary-plane* PUA-B (U+F0000+) where Nerd Fonts v3 relocated
|
|
22
|
+
* the Material Design icon set. Naming the families explicitly makes the
|
|
23
|
+
* browser consult them per character, which covers both planes.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately NOT listed: color-emoji families. Chromium routes genuine
|
|
26
|
+
* emoji code points through a dedicated emoji fallback path (which is why
|
|
27
|
+
* emoji already render), so naming them buys nothing here — while placing a
|
|
28
|
+
* color font ahead of the generic family risks capturing BMP symbols the
|
|
29
|
+
* prompt expects in monospace (U+26A0 ⚠, U+2714 ✔ …) and rendering them as
|
|
30
|
+
* wide color glyphs that break the cell grid.
|
|
31
|
+
*
|
|
32
|
+
* Ordering rationale: the symbols-only patches ship glyphs without Latin,
|
|
33
|
+
* so they can never hijack ASCII metrics — the safest first hop. The
|
|
34
|
+
* fully-patched distributions follow for users who installed one of those
|
|
35
|
+
* instead. Both the `… Mono` and proportional family names are listed
|
|
36
|
+
* because the Nerd Fonts installers register them as distinct families.
|
|
37
|
+
*
|
|
38
|
+
* These are strictly *appended*, never prepended: xterm derives its cell
|
|
39
|
+
* metrics from the first entry, so the base font must stay in front or the
|
|
40
|
+
* whole grid would be re-measured against an icon font.
|
|
41
|
+
*/
|
|
42
|
+
export const ICON_FONT_FALLBACKS: readonly string[] = [
|
|
43
|
+
// Symbols-only patches (glyph coverage without Latin) — ideal fallbacks.
|
|
44
|
+
'"Symbols Nerd Font Mono"',
|
|
45
|
+
'"Symbols Nerd Font"',
|
|
46
|
+
// Common fully-patched Nerd Font distributions.
|
|
47
|
+
'"Hack Nerd Font Mono"',
|
|
48
|
+
'"Hack Nerd Font"',
|
|
49
|
+
'"JetBrainsMono Nerd Font Mono"',
|
|
50
|
+
'"JetBrainsMono Nerd Font"',
|
|
51
|
+
'"FiraCode Nerd Font Mono"',
|
|
52
|
+
'"FiraCode Nerd Font"',
|
|
53
|
+
'"CaskaydiaCove Nerd Font Mono"',
|
|
54
|
+
'"CaskaydiaCove Nerd Font"',
|
|
55
|
+
'"SauceCodePro Nerd Font Mono"',
|
|
56
|
+
'"UbuntuMono Nerd Font Mono"',
|
|
57
|
+
'"Iosevka Nerd Font Mono"',
|
|
58
|
+
'"MesloLGS Nerd Font Mono"',
|
|
59
|
+
'"MesloLGS NF"',
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
/** The symbols-only patch count at the head of {@link ICON_FONT_FALLBACKS}
|
|
63
|
+
* (these carry no Latin glyphs, so they can never hijack ASCII metrics). */
|
|
64
|
+
const SYMBOLS_ONLY_COUNT = 2
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* CSS generic font families. A generic is a catch-all that always resolves,
|
|
68
|
+
* so icon fonts must be spliced in *before* the first one to stay reachable.
|
|
69
|
+
*/
|
|
70
|
+
const GENERIC_FAMILIES = new Set([
|
|
71
|
+
'monospace', 'serif', 'sans-serif', 'cursive', 'fantasy',
|
|
72
|
+
'system-ui', 'ui-monospace', 'ui-serif', 'ui-sans-serif', 'ui-rounded',
|
|
73
|
+
'math', 'emoji', 'fangsong',
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* CSS-wide keywords. These are only valid as an *entire* declaration value,
|
|
78
|
+
* so a stack cannot be appended to one.
|
|
79
|
+
*/
|
|
80
|
+
const CSS_WIDE_KEYWORDS = new Set(['inherit', 'initial', 'unset', 'revert', 'revert-layer'])
|
|
81
|
+
|
|
82
|
+
/** Normalize one family name for comparison: unquote, collapse runs of
|
|
83
|
+
* whitespace, casefold. */
|
|
84
|
+
function normalizeFamily(family: string): string {
|
|
85
|
+
return family
|
|
86
|
+
.trim()
|
|
87
|
+
.replace(/^["']|["']$/g, '')
|
|
88
|
+
.replace(/\s+/g, ' ')
|
|
89
|
+
.trim()
|
|
90
|
+
.toLowerCase()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Split a CSS font-family stack on its top-level commas.
|
|
95
|
+
*
|
|
96
|
+
* Naive `split(',')` would corrupt quoted family names containing a comma
|
|
97
|
+
* and function values such as `var(--x, monospace)`, so quotes (with
|
|
98
|
+
* backslash escapes) and parentheses are tracked.
|
|
99
|
+
*
|
|
100
|
+
* @param stack - a CSS font-family stack.
|
|
101
|
+
* @returns the trimmed, non-empty family entries in source order.
|
|
102
|
+
*/
|
|
103
|
+
function splitFamilies(stack: string): string[] {
|
|
104
|
+
const entries: string[] = []
|
|
105
|
+
let buffer = ''
|
|
106
|
+
let quote: string | null = null
|
|
107
|
+
let depth = 0
|
|
108
|
+
for (let i = 0; i < stack.length; i += 1) {
|
|
109
|
+
const char = stack[i] as string
|
|
110
|
+
if (quote !== null) {
|
|
111
|
+
// Escapes are copied verbatim so `\"` does not close the quote.
|
|
112
|
+
if (char === '\\' && i + 1 < stack.length) {
|
|
113
|
+
buffer += char + (stack[i + 1] as string)
|
|
114
|
+
i += 1
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
buffer += char
|
|
118
|
+
if (char === quote) quote = null
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
if (char === '"' || char === "'") {
|
|
122
|
+
quote = char
|
|
123
|
+
buffer += char
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
if (char === '(') depth += 1
|
|
127
|
+
else if (char === ')') depth = Math.max(0, depth - 1)
|
|
128
|
+
else if (char === ',' && depth === 0) {
|
|
129
|
+
entries.push(buffer)
|
|
130
|
+
buffer = ''
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
buffer += char
|
|
134
|
+
}
|
|
135
|
+
entries.push(buffer)
|
|
136
|
+
return entries.map(entry => entry.trim()).filter(entry => entry !== '')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Append {@link ICON_FONT_FALLBACKS} to a CSS font-family stack, keeping
|
|
141
|
+
* the caller's own entries and order intact.
|
|
142
|
+
*
|
|
143
|
+
* - Families already named in `stack` are not duplicated (quote-, case- and
|
|
144
|
+
* whitespace-insensitive), so a user who already lists their Nerd Font
|
|
145
|
+
* keeps their exact priority.
|
|
146
|
+
* - The icon fonts are spliced in ahead of the *first* generic family
|
|
147
|
+
* (`monospace` etc.), because a generic always resolves: anything after it
|
|
148
|
+
* would never be consulted. A stack that OPENS with a generic is the one
|
|
149
|
+
* exception: only the symbols-only patches (no Latin) may precede it — a
|
|
150
|
+
* fully-patched Nerd Font there would become xterm's measuring base font
|
|
151
|
+
* and override the user/theme family precedence, so it is placed after the
|
|
152
|
+
* generic instead.
|
|
153
|
+
* - Idempotent — re-applying to an already-topped-up stack is a no-op, which
|
|
154
|
+
* matters because `TerminalView` diffs the resolved value against the live
|
|
155
|
+
* `term.options.fontFamily` before reflowing.
|
|
156
|
+
*
|
|
157
|
+
* @param stack - a CSS font-family stack (base font first).
|
|
158
|
+
* @returns the stack with icon fallbacks merged in.
|
|
159
|
+
*/
|
|
160
|
+
export function withIconFontFallbacks(stack: string): string {
|
|
161
|
+
const entries = splitFamilies(stack)
|
|
162
|
+
if (entries.length === 0) return ICON_FONT_FALLBACKS.join(', ')
|
|
163
|
+
|
|
164
|
+
const present = new Set(entries.map(normalizeFamily))
|
|
165
|
+
const notPresent = (family: string): boolean => !present.has(normalizeFamily(family))
|
|
166
|
+
const symbolsOnly = ICON_FONT_FALLBACKS.slice(0, SYMBOLS_ONLY_COUNT).filter(notPresent)
|
|
167
|
+
const patched = ICON_FONT_FALLBACKS.slice(SYMBOLS_ONLY_COUNT).filter(notPresent)
|
|
168
|
+
if (symbolsOnly.length === 0 && patched.length === 0) return entries.join(', ')
|
|
169
|
+
|
|
170
|
+
const firstGeneric = entries.findIndex(entry => GENERIC_FAMILIES.has(normalizeFamily(entry)))
|
|
171
|
+
const cut = firstGeneric === -1 ? entries.length : firstGeneric
|
|
172
|
+
// A fully-patched distribution ships Latin glyphs: placed ahead of a
|
|
173
|
+
// LEADING generic (cut === 0) it would become xterm's measuring/base font
|
|
174
|
+
// and override the user/theme family precedence. Only the symbols-only
|
|
175
|
+
// patches — which carry no Latin — may precede it; the patched fonts go
|
|
176
|
+
// after the generic (still reachable per character, never the base).
|
|
177
|
+
if (cut === 0) {
|
|
178
|
+
return [...symbolsOnly, entries[0]!, ...patched, ...entries.slice(1)].join(', ')
|
|
179
|
+
}
|
|
180
|
+
return [...entries.slice(0, cut), ...symbolsOnly, ...patched, ...entries.slice(cut)].join(', ')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Reduce one link of the base-family chain to a usable stack, or `''` when
|
|
185
|
+
* it cannot carry appended fallbacks so the next link should win.
|
|
186
|
+
*
|
|
187
|
+
* Rejecting CSS-wide keywords matters because the theme font arrives as a
|
|
188
|
+
* raw token value (`tokenValue('--ds-font-family-code')`); skins do set
|
|
189
|
+
* tokens to `initial`/`inherit`/`unset` (see `effectiveTokenValue` in
|
|
190
|
+
* `theme.ts`, which guards the color tokens for the same reason). Appending
|
|
191
|
+
* to such a value yields an invalid `font-family`, which the CSSOM discards
|
|
192
|
+
* silently — the terminal would lose the theme font *and* the icon fonts.
|
|
193
|
+
*/
|
|
194
|
+
function usableBase(value: string | undefined): string {
|
|
195
|
+
const trimmed = (value ?? '').trim()
|
|
196
|
+
if (trimmed === '') return ''
|
|
197
|
+
if (CSS_WIDE_KEYWORDS.has(trimmed.toLowerCase())) return ''
|
|
198
|
+
return trimmed
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Resolve the xterm font options for the given prefs.
|
|
203
|
+
*
|
|
204
|
+
* The base family keeps its existing precedence — user pref > theme code
|
|
205
|
+
* font > built-in stack — and then {@link withIconFontFallbacks} tops it up
|
|
206
|
+
* so prompt icons resolve regardless of which base won.
|
|
207
|
+
*
|
|
208
|
+
* @param prefs - the current side card preferences.
|
|
209
|
+
* @param themeFontFamily - the app's theme code font (`--ds-font-family-code`
|
|
210
|
+
* token value, read live by the caller); undefined when the token is absent.
|
|
211
|
+
* @returns the `fontFamily` / `fontSize` xterm options.
|
|
212
|
+
*/
|
|
213
|
+
export function resolveTerminalFont(
|
|
214
|
+
prefs: SidebarPrefs,
|
|
215
|
+
themeFontFamily: string | undefined,
|
|
216
|
+
): { fontFamily: string; fontSize: number } {
|
|
217
|
+
const base = usableBase(prefs.terminalFontFamily)
|
|
218
|
+
|| usableBase(themeFontFamily)
|
|
219
|
+
|| DEFAULT_TERMINAL_FONT_FAMILY
|
|
220
|
+
return {
|
|
221
|
+
fontFamily: withIconFontFallbacks(base),
|
|
222
|
+
fontSize: clampTerminalFontSize(prefs.terminalFontSize),
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal URL hyperlinks: xterm's `registerLinkProvider` is fed per-line
|
|
3
|
+
* link descriptors built from the pty stream, so URLs printed by any tool
|
|
4
|
+
* become hoverable / clickable spans.
|
|
5
|
+
*
|
|
6
|
+
* To stay out of the user's way (a terminal's primary interaction is text
|
|
7
|
+
* selection, not browsing), a click only activates when the user holds
|
|
8
|
+
* Ctrl (Win/Linux) or Cmd (mac) — a plain click is left for xterm's
|
|
9
|
+
* normal selection handling. Only http(s) URLs are dispatched to the
|
|
10
|
+
* browser; other schemes (`file://`, `mailto:`, `javascript:`, …) are
|
|
11
|
+
* underlined for visibility but rejected at activation, so a `file://`
|
|
12
|
+
* URL printed by a tool stays inert instead of being handed to
|
|
13
|
+
* `window.open`.
|
|
14
|
+
*
|
|
15
|
+
* Kept as a pure module (no xterm import) so the regex, the line
|
|
16
|
+
* scanner, the modifier gate and the scheme guard are unit-testable
|
|
17
|
+
* without mounting a terminal. `buildTerminalLinks` returns plain-object
|
|
18
|
+
* descriptors whose shape matches xterm's `ILink` minus the `activate`
|
|
19
|
+
* callback — the caller attaches `activate` (which closes over the
|
|
20
|
+
* event modifier check + `openTerminalUrl`) so this module never
|
|
21
|
+
* imports xterm types.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** Scheme allowlist for activation. Only http(s) is opened externally. */
|
|
25
|
+
const OPENABLE_SCHEMES = new Set(['http:', 'https:'])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The URL pattern used to scan each terminal line.
|
|
29
|
+
*
|
|
30
|
+
* A word boundary (`\b`) guards the leading scheme so `notttps://…` does
|
|
31
|
+
* not match. The character class excludes ASCII whitespace and the
|
|
32
|
+
* wrapping punctuation that shells commonly emit around URLs (quotes,
|
|
33
|
+
* angle brackets, brackets/braces, pipes, backslashes, backticks) so a
|
|
34
|
+
* URL printed as `"https://example.com"` or `<https://example.com>` does
|
|
35
|
+
* not drag the wrapping character into the link target.
|
|
36
|
+
*
|
|
37
|
+
* Carries the `g` flag so `findTerminalUrlsInLine` can iterate every
|
|
38
|
+
* match on a line; callers must reset `lastIndex` before reuse (the
|
|
39
|
+
* helper does this defensively).
|
|
40
|
+
*/
|
|
41
|
+
export const TERMINAL_URL_REGEX = /\bhttps?:\/\/[^\s"'<>\[\]{}|\\^`]+/gi
|
|
42
|
+
|
|
43
|
+
/** A URL match in a terminal line: the text and its 0-based start offset. */
|
|
44
|
+
export interface TerminalUrlMatch {
|
|
45
|
+
/** 0-based start index of the URL within the line string. */
|
|
46
|
+
start: number
|
|
47
|
+
/** The matched URL text. */
|
|
48
|
+
text: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Strip trailing closing parens that are not balanced by an opening paren
|
|
53
|
+
* earlier in the URL.
|
|
54
|
+
*
|
|
55
|
+
* The regex's character class keeps `(` and `)` (Wikipedia-style URLs
|
|
56
|
+
* carry real parens: `…/Python_(programming_language)`), so a URL wrapped
|
|
57
|
+
* in parens by a shell (`(https://example.com)`) captures the trailing
|
|
58
|
+
* `)`. Balanced pairs are kept intact; only the unmatched excess is
|
|
59
|
+
* trimmed, so:
|
|
60
|
+
* - `https://example.com)` → `https://example.com`
|
|
61
|
+
* - `https://en.wikipedia.org/wiki/Python_(programming_language)` → unchanged
|
|
62
|
+
* - `https://example.com/(` → unchanged (more openers than closers; the
|
|
63
|
+
* trailing `(` is the URL's own, not a wrapper)
|
|
64
|
+
*/
|
|
65
|
+
function trimUnbalancedTrailingParens(url: string): string {
|
|
66
|
+
let opens = 0
|
|
67
|
+
let closers = 0
|
|
68
|
+
for (let i = 0; i < url.length; i += 1) {
|
|
69
|
+
const ch = url[i]
|
|
70
|
+
if (ch === '(') opens += 1
|
|
71
|
+
else if (ch === ')') closers += 1
|
|
72
|
+
}
|
|
73
|
+
const excess = closers - opens
|
|
74
|
+
if (excess <= 0) return url
|
|
75
|
+
let end = url.length
|
|
76
|
+
let stripped = 0
|
|
77
|
+
while (end > 0 && url[end - 1] === ')' && stripped < excess) {
|
|
78
|
+
end -= 1
|
|
79
|
+
stripped += 1
|
|
80
|
+
}
|
|
81
|
+
return url.slice(0, end)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Find every http(s) URL in a line of terminal text, in source order
|
|
86
|
+
* with 0-based start offsets. A link provider maps these to buffer
|
|
87
|
+
* ranges for xterm's `registerLinkProvider`.
|
|
88
|
+
*
|
|
89
|
+
* Trailing unmatched closing parens are trimmed from each match (see
|
|
90
|
+
* {@link trimUnbalancedTrailingParens}), so a URL wrapped in parens by
|
|
91
|
+
* a shell opens without the trailing `)`, while Wikipedia-style URLs
|
|
92
|
+
* with balanced parens stay intact.
|
|
93
|
+
*
|
|
94
|
+
* Resets the regex's `lastIndex` before and after the scan so a
|
|
95
|
+
* previous partial iteration can't desynchronize a later one (the
|
|
96
|
+
* regex carries the `g` flag and is module-shared).
|
|
97
|
+
*/
|
|
98
|
+
export function findTerminalUrlsInLine(line: string): TerminalUrlMatch[] {
|
|
99
|
+
TERMINAL_URL_REGEX.lastIndex = 0
|
|
100
|
+
const matches: TerminalUrlMatch[] = []
|
|
101
|
+
let m: RegExpExecArray | null
|
|
102
|
+
while ((m = TERMINAL_URL_REGEX.exec(line)) !== null) {
|
|
103
|
+
const trimmed = trimUnbalancedTrailingParens(m[0])
|
|
104
|
+
if (trimmed.length > 0) {
|
|
105
|
+
matches.push({ start: m.index, text: trimmed })
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
TERMINAL_URL_REGEX.lastIndex = 0
|
|
109
|
+
return matches
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A buffer range in xterm's coordinate system. xterm's cell `x` is
|
|
114
|
+
* 1-based (1..cols) and `y` is the buffer line number; the `end` is
|
|
115
|
+
* inclusive (the last cell covered by the link). Structurally
|
|
116
|
+
* compatible with xterm's `IBufferRange` so the caller can pass it
|
|
117
|
+
* straight through without importing xterm types here.
|
|
118
|
+
*/
|
|
119
|
+
export interface TerminalLinkRange {
|
|
120
|
+
start: { x: number; y: number }
|
|
121
|
+
end: { x: number; y: number }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A link descriptor: a buffer range plus the URL text. The caller
|
|
126
|
+
* (the link provider in `TerminalView`) attaches the `activate`
|
|
127
|
+
* callback, which closes over the modifier gate and `openTerminalUrl`.
|
|
128
|
+
*/
|
|
129
|
+
export interface TerminalLinkDescriptor {
|
|
130
|
+
range: TerminalLinkRange
|
|
131
|
+
text: string
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Build link descriptors for every URL found in a terminal line.
|
|
136
|
+
*
|
|
137
|
+
* @param lineText - the line's text (e.g. from
|
|
138
|
+
* `IBufferLine.translateToString(true)`).
|
|
139
|
+
* @param lineNumber - the buffer line number xterm passed to
|
|
140
|
+
* `ILinkProvider.provideLinks` (used as the `y` of every range; URLs
|
|
141
|
+
* never span wrapped lines because each wrapped row is its own
|
|
142
|
+
* buffer line).
|
|
143
|
+
* @returns descriptors in source order; empty when the line has no URL.
|
|
144
|
+
*/
|
|
145
|
+
export function buildTerminalLinks(lineText: string, lineNumber: number): TerminalLinkDescriptor[] {
|
|
146
|
+
return findTerminalUrlsInLine(lineText).map(({ start, text: url }) => ({
|
|
147
|
+
range: {
|
|
148
|
+
// xterm's cell `x` is 1-based; `start` is 0-based from
|
|
149
|
+
// translateToString, so the first URL character sits at x = start + 1.
|
|
150
|
+
start: { x: start + 1, y: lineNumber },
|
|
151
|
+
// `end` is inclusive: the last URL character is at 0-based
|
|
152
|
+
// (start + url.length - 1), which is 1-based (start + url.length).
|
|
153
|
+
end: { x: start + url.length, y: lineNumber },
|
|
154
|
+
},
|
|
155
|
+
text: url,
|
|
156
|
+
}))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Decide whether a click on a terminal link should activate (open the URL).
|
|
161
|
+
*
|
|
162
|
+
* Mirrors what every modern terminal does (Windows Terminal, iTerm2, the
|
|
163
|
+
* VSCode integrated terminal): a plain click stays a text-selection
|
|
164
|
+
* gesture, and only Ctrl (Win/Linux) or Cmd (mac) hands the URL to the
|
|
165
|
+
* browser. The modifier is read off the activating `MouseEvent`, not
|
|
166
|
+
* tracked separately, so a key-up between hover and click never
|
|
167
|
+
* desynchronizes the gate.
|
|
168
|
+
*/
|
|
169
|
+
export function shouldActivateTerminalLink(event: MouseEvent): boolean {
|
|
170
|
+
return event.ctrlKey || event.metaKey
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Open a URL matched in the terminal, with a scheme guard so a printed
|
|
175
|
+
* `file://` or anything that slipped past the regex cannot reach
|
|
176
|
+
* `window.open`. The URL is constructed via `new URL(...)` which throws
|
|
177
|
+
* on malformed input; the catch makes the function total so the xterm
|
|
178
|
+
* handler never throws into the terminal's event loop.
|
|
179
|
+
*
|
|
180
|
+
* @returns `true` when the URL was dispatched to `window.open`, `false`
|
|
181
|
+
* when it was rejected (bad URL, disallowed scheme, no `window`).
|
|
182
|
+
*/
|
|
183
|
+
export function openTerminalUrl(uri: string): boolean {
|
|
184
|
+
if (typeof window === 'undefined') return false
|
|
185
|
+
let url: URL
|
|
186
|
+
try {
|
|
187
|
+
url = new URL(uri)
|
|
188
|
+
} catch {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
if (!OPENABLE_SCHEMES.has(url.protocol)) return false
|
|
192
|
+
window.open(url.toString(), '_blank', 'noopener,noreferrer')
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live theme access for surfaces that cannot consume the token colors
|
|
3
|
+
* directly — xterm's palette and CodeMirror's theme extensions need concrete
|
|
4
|
+
* values, but the app's scheme flips at runtime (ui-layout's ThemePresenter
|
|
5
|
+
* projects prefers-color-scheme and the user's choice onto
|
|
6
|
+
* body[data-ds-dark-theme] and html { color-scheme }). This module reads the
|
|
7
|
+
* resolved scheme and token values, and notifies subscribers on flips, so
|
|
8
|
+
* the terminal and the editor re-theme in place instead of freezing in the
|
|
9
|
+
* scheme they happened to be created under.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Whether the app shell resolved to the dark scheme.
|
|
13
|
+
*
|
|
14
|
+
* The presenter sets `html { color-scheme }` together with the body palette
|
|
15
|
+
* attribute, so a set color-scheme means the decision is authoritative (an
|
|
16
|
+
* absent attribute is then LIGHT even when the OS prefers dark — the user
|
|
17
|
+
* chose light). Before the presenter has run, fall back to the OS media
|
|
18
|
+
* query as the best guess.
|
|
19
|
+
*/
|
|
20
|
+
export function isDarkScheme(): boolean {
|
|
21
|
+
if (typeof document === 'undefined') return true
|
|
22
|
+
const decided = document.documentElement.style.colorScheme !== ''
|
|
23
|
+
if (decided) return document.body.hasAttribute('data-ds-dark-theme')
|
|
24
|
+
return typeof matchMedia !== 'undefined' && matchMedia('(prefers-color-scheme: dark)').matches
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One token's computed value on <body> ('' while the theme has not applied). */
|
|
28
|
+
export function tokenValue(name: string): string {
|
|
29
|
+
if (typeof document === 'undefined') return ''
|
|
30
|
+
return getComputedStyle(document.body).getPropertyValue(name).trim()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Minimal alpha for a token color to count as effectively opaque. Skin
|
|
34
|
+
* systems turn `--dsw-alias-bg-base` translucent for glass panels (the
|
|
35
|
+
* dsh-web-ui skins use rgba 0.16–0.7; `transparent` is 0); below this
|
|
36
|
+
* floor a text surface (terminal, editor) would render over the skin's
|
|
37
|
+
* backdrop art, so callers fall back to an opaque color. Values at or
|
|
38
|
+
* above the floor (e.g. a skin's scoped 0.96 porcelain) pass through —
|
|
39
|
+
* the skin still controls the surface. */
|
|
40
|
+
const OPAQUE_ALPHA_MIN = 0.9
|
|
41
|
+
|
|
42
|
+
/** The alpha channel of a computed CSS color, or null when the format is
|
|
43
|
+
* not parseable (named colors, `color()`… — treated as opaque). Handles
|
|
44
|
+
* the shapes getComputedStyle actually returns: the rgb()/rgba() and
|
|
45
|
+
* hsl()/hsla() function forms (comma or space syntax, with or without the
|
|
46
|
+
* `/ alpha` slot) and the #rgb/#rgba/#rrggbb/#rrggbbaa hex family. */
|
|
47
|
+
export function colorAlpha(color: string): number | null {
|
|
48
|
+
const s = color.trim()
|
|
49
|
+
// #rgb / #rgba / #rrggbb / #rrggbbaa
|
|
50
|
+
const hex = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(s)
|
|
51
|
+
if (hex !== null) {
|
|
52
|
+
const digits = hex[1]!
|
|
53
|
+
if (digits.length === 3 || digits.length === 4) {
|
|
54
|
+
const a = digits.length === 4 ? digits[3]! : 'f'
|
|
55
|
+
return parseInt(a + a, 16) / 255
|
|
56
|
+
}
|
|
57
|
+
const alphaHex = digits.length === 8 ? digits.slice(6) : 'ff'
|
|
58
|
+
return parseInt(alphaHex, 16) / 255
|
|
59
|
+
}
|
|
60
|
+
// rgb(r g b) / rgb(r g b / a) / rgba(r, g, b, a) — the trailing slot is alpha.
|
|
61
|
+
const fn = /^(rgba?|hsla?)\(([^)]+)\)$/i.exec(s)
|
|
62
|
+
if (fn !== null) {
|
|
63
|
+
const parts = fn[2]!.split(/[,\s/]+/).filter(Boolean)
|
|
64
|
+
const alphaPart = parts[3]
|
|
65
|
+
if (alphaPart === undefined) return 1
|
|
66
|
+
const alpha = Number.parseFloat(alphaPart)
|
|
67
|
+
return Number.isFinite(alpha) ? alpha : 1
|
|
68
|
+
}
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A token value that actually PAINTS something — the guard for text
|
|
74
|
+
* surfaces (issue #90). Skin systems routinely set global tokens to
|
|
75
|
+
* `transparent` (glass skins) or translucent glass values (`rgba(…,0.16–0.7)`,
|
|
76
|
+
* e.g. the dsh-web-ui skins) — both are truthy strings, so callers using
|
|
77
|
+
* `|| fallback` never fire and the terminal/editor goes see-through over
|
|
78
|
+
* the skin's backdrop. This returns '' for visually inert values (unset
|
|
79
|
+
* keywords, transparent, and any color below the opacity floor) so the
|
|
80
|
+
* caller's fallback chain engages; effectively opaque values pass through.
|
|
81
|
+
*/
|
|
82
|
+
export function effectiveTokenValue(name: string): string {
|
|
83
|
+
const raw = tokenValue(name)
|
|
84
|
+
switch (raw) {
|
|
85
|
+
case '':
|
|
86
|
+
case 'transparent':
|
|
87
|
+
case 'initial':
|
|
88
|
+
case 'inherit':
|
|
89
|
+
case 'unset':
|
|
90
|
+
return ''
|
|
91
|
+
default: {
|
|
92
|
+
const alpha = colorAlpha(raw)
|
|
93
|
+
if (alpha !== null && alpha < OPAQUE_ALPHA_MIN) return ''
|
|
94
|
+
return raw
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Subscribe to color-scheme flips (the presenter toggles the body
|
|
101
|
+
* attribute). The callback fires after the attribute changed; re-read the
|
|
102
|
+
* scheme inside it.
|
|
103
|
+
* @returns the disposer.
|
|
104
|
+
*/
|
|
105
|
+
export function subscribeColorScheme(callback: () => void): () => void {
|
|
106
|
+
if (typeof document === 'undefined') return () => {}
|
|
107
|
+
const observer = new MutationObserver(() => { callback() })
|
|
108
|
+
observer.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })
|
|
109
|
+
return () => { observer.disconnect() }
|
|
110
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The title-bar strip resolution chain — the ONE place that decides how
|
|
3
|
+
* many pixels the sidebar yields at the top. Standard signals first, then
|
|
4
|
+
* the user's chosen scheme; never a per-shell branch:
|
|
5
|
+
*
|
|
6
|
+
* 0. `web` scheme — EXPLICIT "ChatCode CLI Web": never adapt, not even
|
|
7
|
+
* standard WCO geometry (the user declares the plain web UI).
|
|
8
|
+
* 1. Window Controls Overlay real geometry (standard API, authoritative
|
|
9
|
+
* when present — even 0, e.g. the overlay is hidden while maximized).
|
|
10
|
+
* 2. The `dsh-desktop-titlebar-inset` URL contract parameter (a shell
|
|
11
|
+
* declares the exact pixels it reserves).
|
|
12
|
+
* 3. The active shell preset's strip (scheme `preset` — opt-in data).
|
|
13
|
+
* 4. The legacy manual `titleBarStripPx` (scheme `custom`).
|
|
14
|
+
* 5. 0 — plain-browser semantics, nothing modified.
|
|
15
|
+
*
|
|
16
|
+
* The result drives `--dsh-title-bar-strip` + `body[data-dsh-title-bar-compat]`
|
|
17
|
+
* exactly like the legacy boolean did; only the VALUE source changed.
|
|
18
|
+
*/
|
|
19
|
+
import type { DesktopEnv } from './desktop-env.ts'
|
|
20
|
+
import type { TitleBarScheme } from '../prefs-shared.ts'
|
|
21
|
+
import type { WcoSnapshot } from './wco.ts'
|
|
22
|
+
import { presetStripFor, type ShellPreset } from './shell-presets.ts'
|
|
23
|
+
|
|
24
|
+
export function computeTitleBarStrip(
|
|
25
|
+
env: DesktopEnv,
|
|
26
|
+
wco: WcoSnapshot,
|
|
27
|
+
scheme: TitleBarScheme,
|
|
28
|
+
preset: ShellPreset | undefined,
|
|
29
|
+
customStripPx: number,
|
|
30
|
+
): number {
|
|
31
|
+
if (scheme === 'web') return 0
|
|
32
|
+
if (wco.present) return wco.height
|
|
33
|
+
if (env.titlebarInset > 0) return env.titlebarInset
|
|
34
|
+
if (scheme === 'preset') return presetStripFor(preset, env) ?? 0
|
|
35
|
+
if (scheme === 'custom') return customStripPx
|
|
36
|
+
return 0
|
|
37
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official app icons for the 打开项目 menu — JetBrains product marks are
|
|
3
|
+
* the full-color SVGs from resources.jetbrains.com (gradient ids namespaced
|
|
4
|
+
* per icon so several can coexist on one page), VS Code from simple-icons,
|
|
5
|
+
* Explorer a filled Windows-yellow folder. Sizing lives in CSS.
|
|
6
|
+
* @module dsh-ui-tweaks/client/appicons
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** target key -> inline SVG markup (trusted build-time constants). */
|
|
10
|
+
export const APP_ICONS: {
|
|
11
|
+
explorer: string
|
|
12
|
+
vscode: string
|
|
13
|
+
idea: string
|
|
14
|
+
goland: string
|
|
15
|
+
webstorm: string
|
|
16
|
+
pycharm: string
|
|
17
|
+
} = {
|
|
18
|
+
explorer: '<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="#FFB900" d="M1.5 3.2A1.7 1.7 0 0 1 3.2 1.5h3.1l1.7 2.1h4.8A1.7 1.7 0 0 1 14.5 5.3v7.5a1.7 1.7 0 0 1-1.7 1.7H3.2a1.7 1.7 0 0 1-1.7-1.7Z"/></svg>',
|
|
19
|
+
vscode: "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"#007ACC\" d=\"M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z\"/></svg>",
|
|
20
|
+
idea: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 64 64\"> <defs> <linearGradient id=\"gbi-idea-a\" x1=\"-.717383\" x2=\"24.1455\" y1=\"7.61946\" y2=\"61.2456\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\".1\" stop-color=\"#FC801D\"/> <stop offset=\".59\" stop-color=\"#FE2857\"/> </linearGradient> <linearGradient id=\"gbi-idea-b\" x1=\"4.22243\" x2=\"62.9273\" y1=\"60.0186\" y2=\"1.31316\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\".21\" stop-color=\"#FE2857\"/> <stop offset=\".7\" stop-color=\"#007EFF\"/> </linearGradient> </defs> <path fill=\"#FF8100\" d=\"m15.9476 5.81641-11.87545.00174C1.82284 5.81815 0 7.64157 0 9.89088V21.3975c0 1.1887.519564 2.3185 1.42196 3.0924L39.5828 57.1997c.7384.6324 1.6786.9803 2.6508.9803h11.8755c2.2493 0 4.0727-1.8234 4.0727-4.0727V42.599c0-1.1887-.5195-2.3186-1.4219-3.0924L18.599 6.79735c-.7383-.63302-1.6786-.98036-2.6514-.98036v-.00058Z\"/> <path fill=\"url(#gbi-idea-a)\" d=\"M14.5193 5.81641H4.07273C1.82342 5.81641 0 7.63982 0 9.88913V22.9818c0 .1926.013964.3852.040727.576L5.31782 60.5015c.28683 2.0067 2.00494 3.4967 4.032 3.4967H25.0228c2.2499 0 4.0733-1.824 4.0728-4.0739l-.0047-18.5384c0-.4376-.0704-.8722-.2089-1.287L18.3825 8.60099c-.5544-1.66284-2.1108-2.78458-3.8638-2.78458h.0006Z\"/> <path fill=\"url(#gbi-idea-b)\" d=\"M59.9275 0H25.9592c-1.6291 0-3.1017.971054-3.7435 2.46807L6.14767 39.9587c-.21702.5068-.32931 1.0531-.32931 1.6046v18.364C5.81836 62.1766 7.64178 64 9.89109 64H27.8571c.8046 0 1.5912-.2385 2.2609-.6854l32.0687-21.4033c1.1322-.7552 1.8117-2.0265 1.8117-3.3874l.0018-34.45117C64.0002 1.82342 62.1768 0 59.9275 0Z\"/> <path fill=\"#000\" d=\"M52 12H12v40h40V12Z\"/> <path fill=\"#fff\" d=\"M17 29.3856h2.9788v-9.7712H17V17h8.839v2.6144h-2.9788v9.7712h2.9788V32H17v-2.6144Z\"/> <path fill=\"#fff\" d=\"M27.3389 29.3002h2.1538c.4354 0 .8233-.0928 1.1625-.2784.3392-.1857.6016-.4481.7872-.7873.1857-.3392.2785-.7265.2785-1.1625V17h2.9249v10.2748c0 .9001-.2074 1.7092-.6216 2.4271-.4143.7179-.9855 1.2805-1.7143 1.6873-.7288.4074-1.5464.6108-2.4534.6108h-2.5176v-2.6998Z\"/> <path fill=\"#fff\" d=\"M17 44h16v3H17v-3Z\"/> </svg>",
|
|
21
|
+
goland: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 64 64\"> <defs> <linearGradient id=\"gbi-goland-a\" x1=\"63.6351\" x2=\"39.318\" y1=\"54.0451\" y2=\"1.59593\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\".24\" stop-color=\"#00D886\"/> <stop offset=\".51\" stop-color=\"#007DFE\"/> </linearGradient> <linearGradient id=\"gbi-goland-b\" x1=\"59.3792\" x2=\".674325\" y1=\"4.38051\" y2=\"63.0854\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\".27\" stop-color=\"#007DFE\"/> <stop offset=\".7\" stop-color=\"#D249FC\"/> </linearGradient> </defs> <path fill=\"#00D886\" d=\"m48.0521 58.18 11.8755-.0017c2.2493 0 4.0722-1.8234 4.0722-4.0727V42.599c0-1.1887-.5196-2.3186-1.422-3.0924L24.4164 6.79677c-.7384-.63244-1.6786-.98036-2.6508-.98036H9.89011c-2.24931 0-4.07273 1.82341-4.07273 4.07272V21.3975c0 1.1887.51957 2.3185 1.42197 3.0924L45.4002 57.1991c.7383.633 1.6786.9804 2.6514.9804l.0005.0005Z\"/> <path fill=\"url(#gbi-goland-a)\" d=\"M49.4806 58.1818h10.4465c2.2493 0 4.0728-1.8234 4.0728-4.0727V41.0164c0-.1925-.014-.3851-.0408-.576L58.6815 3.49673C58.3952 1.49062 56.6765 0 54.65 0H38.977c-2.2499 0-4.0733 1.824-4.0727 4.07389l.0047 18.53851c0 .4375.0704.8721.2088 1.2869l10.4995 31.4979c.5545 1.6629 2.1109 2.7846 3.8639 2.7846h-.0006Z\"/> <path fill=\"url(#gbi-goland-b)\" d=\"M4.07273 64H38.041c1.6291 0 3.1017-.9711 3.7434-2.4681l16.0681-37.4906c.217-.5068.3293-1.0531.3293-1.6046V4.07273C58.1818 1.82342 56.3584 0 54.1091 0h-17.966c-.8046 0-1.5912.238545-2.2609.685382L1.81353 22.0881C.681309 22.8439.001745 24.1146.001745 25.4755L0 59.9273C0 62.1766 1.82342 64 4.07273 64Z\"/> <path fill=\"#000\" d=\"M52 12H12v40h40V12Z\"/> <path fill=\"#fff\" d=\"M19.764 31.6245c1.174.6765 2.4847 1.0155 3.9318 1.0155 1.3899 0 2.6427-.3095 3.7589-.9293 1.1231-.6197 2.005-1.4651 2.6461-2.5384.6481-1.0798.9721-2.2857.9721-3.6183v-1.1879h-6.405v2.3221h3.516c-.0722.5605-.2761 1.0718-.6106 1.5339-.3958.5468-.9322.9762-1.6092 1.2852-.6701.3096-1.4188.4646-2.2469.4646-.8928 0-1.6988-.2227-2.4193-.6695-.7204-.4467-1.2857-1.0583-1.6959-1.836-.4034-.784-.6047-1.667-.6047-2.6461 0-.979.2013-1.858.6047-2.6357.4108-.7846.9761-1.4003 1.6959-1.847.7199-.4467 1.5265-.6695 2.4193-.6695.6053 0 1.1706.0972 1.696.2916.526.1875.9831.4577 1.372.8101.3958.3455.7094.7563.9397 1.2314h3.1542c-.2737-.9866-.7529-1.8575-1.4368-2.6137-.677-.7563-1.5126-1.3436-2.5061-1.7608-.9866-.4172-2.0669-.6267-3.2404-.6267-1.4477 0-2.7583.3414-3.9318 1.0259-1.1741.6765-2.0959 1.6133-2.7653 2.8082-.6695 1.1879-1.0046 2.5165-1.0046 3.9856 0 1.4692.3351 2.8018 1.0046 3.9967.67 1.1879 1.5918 2.1236 2.7653 2.8081Z\"/> <path fill=\"#fff\" fill-rule=\"evenodd\" d=\"M36.1829 31.6245c1.181.6765 2.4951 1.0155 3.9422 1.0155 1.4541 0 2.7688-.339 3.9423-1.0155 1.181-.6845 2.1062-1.6202 2.7757-2.8081.6695-1.1949 1.0045-2.5275 1.0045-3.9967 0-1.4691-.335-2.7977-1.0045-3.9856-.6695-1.1949-1.5947-2.1317-2.7757-2.8082C42.8939 17.3414 41.5723 17 40.1251 17c-1.4471 0-2.7612.3414-3.9422 1.0259-1.1735.6765-2.0988 1.6133-2.7758 2.8082-.6695 1.1879-1.0045 2.5165-1.0045 3.9856 0 1.4692.335 2.8018 1.0045 3.9967.6765 1.1879 1.6023 2.1236 2.7758 2.8081Zm6.3621-2.2463c-.7129.4468-1.516.6695-2.4089.6695-.8928 0-1.6994-.2227-2.4193-.6695-.7128-.4536-1.2747-1.0756-1.685-1.8684-.4033-.7991-.6046-1.696-.6046-2.6895 0-.9935.2013-1.8858.6046-2.6785.4103-.7991.9722-1.4217 1.685-1.8684.7199-.4537 1.5265-.6805 2.4193-.6805.8929 0 1.696.2268 2.4089.6805.7129.4467 1.2707 1.0693 1.674 1.8684.4033.7921.6047 1.685.6047 2.6785s-.2014 1.8904-.6047 2.6895c-.4033.7922-.9611 1.4148-1.674 1.8684Z\" clip-rule=\"evenodd\"/> <path fill=\"#fff\" d=\"M16.9941 44h16v3h-16v-3Z\"/> </svg>",
|
|
22
|
+
webstorm: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 64 64\"> <defs> <linearGradient id=\"gbi-webstorm-a\" x1=\"7.62141\" x2=\"61.2476\" y1=\"64.7192\" y2=\"39.8564\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\".22\" stop-color=\"#F0EB18\"/> <stop offset=\".59\" stop-color=\"#00C4F4\"/> </linearGradient> <linearGradient id=\"gbi-webstorm-b\" x1=\"60.0186\" x2=\"1.31316\" y1=\"59.7777\" y2=\"1.07287\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\".19\" stop-color=\"#00C4F4\"/> <stop offset=\".83\" stop-color=\"#007DFE\"/> </linearGradient> </defs> <path fill=\"#F0EB18\" d=\"m5.81836 48.0512.00174 11.8755c0 2.2493 1.82342 4.0721 4.07273 4.0721H21.3994c1.1887 0 2.3186-.5196 3.0924-1.422l32.7098-38.1614c.6325-.7383.9804-1.6786.9804-2.6508V9.88913c0-2.24931-1.8234-4.07272-4.0727-4.07272H42.6009c-1.1887 0-2.3185.51956-3.0924 1.42196L6.79872 45.3998c-.63302.7383-.98036 1.6786-.98036 2.6514Z\"/> <path fill=\"url(#gbi-webstorm-a)\" d=\"M5.81836 49.4825v10.4466c0 2.2493 1.82342 4.0727 4.07273 4.0727H22.9837c.1926 0 .3852-.014.576-.0407l36.9437-5.2771c2.0067-.2868 3.4968-2.005 3.4968-4.032V38.979c0-2.2499-1.824-4.0733-4.0739-4.0727l-18.5385.0046c-.4375 0-.8721.0704-1.287.2089L8.60294 45.6193c-1.66284.5544-2.78458 2.1108-2.78458 3.8638v-.0006Z\"/> <path fill=\"url(#gbi-webstorm-b)\" d=\"M0 4.07273V38.041c0 1.6291.971054 3.1017 2.46807 3.7434L39.9587 57.8525c.5067.217 1.0531.3293 1.6046.3293h18.364c2.2493 0 4.0727-1.8234 4.0727-4.0727v-17.966c0-.8046-.2386-1.5913-.6854-2.2609L41.9113 1.81353C41.1561.681309 39.8854.001745 38.5239.001745L4.07273 0C1.82342 0 0 1.82342 0 4.07273Z\"/> <path fill=\"#000\" d=\"M52 12H12v40h40V12Z\"/> <path fill=\"#fff\" d=\"m21.2377 27.961-2.4224-10.9532h-3.0321l3.6224 15.0044h3.3226l2.5613-10.8998 2.5292 10.8998h3.333l3.6441-15.0044h-2.9689L29.4797 27.961l-2.7008-10.9532h-2.9581L21.2377 27.961Z\"/> <path fill=\"#fff\" d=\"M38.2224 31.6804c.8429.3927 1.8074.5896 2.8937.5896s2.0515-.1998 2.8944-.6005c.8428-.4008 1.4968-.9503 1.9613-1.6507.4714-.7074.7073-1.5049.7073-2.3903 0-.7222-.1607-1.3865-.4822-1.9934-.3216-.6149-.7717-1.1294-1.3505-1.5434-.5718-.422-1.2252-.7073-1.9613-.8572l-2.551-.5575c-.4644-.1137-.8325-.3066-1.1041-.5787-.2646-.2722-.3967-.615-.3967-1.0289 0-.364.0965-.6815.2894-.9537.1929-.2785.461-.4932.8038-.643.3497-.1568.7539-.236 1.2109-.236.457 0 .8607.0815 1.2109.2463.3571.1567.6321.3818.8251.6752.1929.2854.2893.6109.2893.9755h2.9259c-.0074-.8572-.236-1.614-.6861-2.272-.4432-.6648-1.0645-1.1833-1.8649-1.5542-.8003-.3715-1.7253-.5575-2.7329-.5575-1.0077 0-1.908.1929-2.7009.5788-.7929.3789-1.4107.9106-1.8539 1.5967-.4427.6792-.6643 1.4469-.6643 2.3041 0 .7073.1464 1.3441.4392 1.9079.2934.5644.7039 1.0398 1.2327 1.4256.5288.379 1.1506.6471 1.8649.8038l2.6474.5897c.5076.1223.9037.3393 1.1897.654.2928.3146.4392.7113.4392 1.1896 0 .3859-.1074.7286-.3215 1.0289-.2073.3003-.5036.5357-.8894.7074-.3789.1648-.8004.2463-1.3292.2463-.5288 0-.9967-.0925-1.4038-.2785-.4071-.186-.7251-.4432-.9537-.7716-.2285-.3279-.3427-.7114-.3427-1.1466h-2.9368c.0143.9215.2572 1.7362.7286 2.4436.4788.7073 1.1362 1.2579 1.9722 1.6507Z\"/> <path fill=\"#fff\" d=\"M16.9932 44h16v3h-16v-3Z\"/> </svg>",
|
|
23
|
+
pycharm: "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 64 64\"> <defs> <linearGradient id=\"gbi-pycharm-a\" x1=\"7.62141\" x2=\"61.2476\" y1=\"64.7192\" y2=\"39.8558\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\".1\" stop-color=\"#00D886\"/> <stop offset=\".59\" stop-color=\"#F0EB18\"/> </linearGradient> <linearGradient id=\"gbi-pycharm-b\" x1=\"60.0186\" x2=\"1.31317\" y1=\"59.7778\" y2=\"1.07229\" gradientUnits=\"userSpaceOnUse\"> <stop offset=\".3\" stop-color=\"#F0EB18\"/> <stop offset=\".7\" stop-color=\"#00C4F4\"/> </linearGradient> </defs> <path fill=\"#00D886\" d=\"m5.81934 48.0512.00174 11.8755c0 2.2493 1.82342 4.0721 4.07273 4.0721H21.4004c1.1887 0 2.3186-.5196 3.0924-1.422L57.202 24.4154c.6325-.7383.9804-1.6786.9804-2.6508V9.88913c0-2.24931-1.8234-4.07272-4.0727-4.07272H42.6013c-1.1887 0-2.3185.51956-3.0924 1.42196L6.7997 45.3998c-.63302.7384-.98036 1.6786-.98036 2.6514Z\"/> <path fill=\"url(#gbi-pycharm-a)\" d=\"M5.81836 49.4825v10.4466c0 2.2493 1.82342 4.0727 4.07273 4.0727H22.9837c.1926 0 .3852-.0139.576-.0407l36.9438-5.2771c2.0066-.2868 3.4967-2.0049 3.4967-4.032V38.979c0-2.2499-1.824-4.0733-4.0739-4.0727l-18.5385.0046c-.4375 0-.8721.0704-1.287.2089L8.60294 45.6193c-1.66284.5544-2.78458 2.1108-2.78458 3.8638v-.0006Z\"/> <path fill=\"url(#gbi-pycharm-b)\" d=\"M0 4.07273V38.041c0 1.6291.971054 3.1017 2.46807 3.7434L39.9587 57.8525c.5068.217 1.0531.3293 1.6046.3293h18.364c2.2493 0 4.0727-1.8234 4.0727-4.0727v-17.966c0-.8046-.2385-1.5912-.6854-2.2609L41.9119 1.81353C41.1561.681309 39.8854.001745 38.5245.001745L4.07273 0C1.82342 0 0 1.82342 0 4.07273Z\"/> <path fill=\"#000\" d=\"M52 12H12v40h40V12Z\"/> <path fill=\"#fff\" fill-rule=\"evenodd\" d=\"M23.5363 16.9676h-6.4407v15.0055h2.9261v-5.6702h3.4296c1.0715 0 2.0115-.1929 2.8188-.5788.8148-.3927 1.4401-.9434 1.8759-1.6508.4427-.7074.6643-1.5434.6643-2.465 0-.9216-.2182-1.7324-.654-2.4329-.4289-.7005-1.0433-1.2431-1.8437-1.629-.8005-.3859-1.7261-.5788-2.7763-.5788Zm1.0927 6.6349c-.3646.1785-.7929.2681-1.2862.2681h-3.3229v-4.4695h3.3229c.4933 0 .9216.0924 1.2862.2784.3715.178.6569.4353.8573.7718.2004.3278.3003.7286.3003 1.1788 0 .4502-.1005.8469-.3003 1.1897-.1998.3365-.4858.5966-.8573.7827Z\" clip-rule=\"evenodd\"/> <path fill=\"#fff\" d=\"M33.3821 31.2232c1.1651.6713 2.4656 1.0077 3.9017 1.0077v-.0011c1.2144 0 2.3295-.2251 3.3441-.6753 1.0146-.4501 1.8575-1.0783 2.5294-1.8862.6787-.8148 1.1328-1.7473 1.3614-2.7975H41.453c-.2004.5426-.5001 1.0221-.9003 1.4361-.3933.4076-.8688.7223-1.4257.9434-.557.221-1.1645.3324-1.822.3324-.886 0-1.6864-.221-2.4007-.6643-.7149-.4433-1.2759-1.0502-1.683-1.8219-.4002-.7781-.6-1.6543-.6-2.6259 0-.9715.1998-1.8437.6-2.6154.4077-.7786.9681-1.3896 1.683-1.8329.7143-.4432 1.5147-.6643 2.4007-.6643.6569 0 1.2644.1114 1.822.3325.5575.221 1.0324.5397 1.4257.9537.4002.4077.6999.8831.9003 1.4257h3.0657c-.2291-1.0502-.6827-1.9787-1.3614-2.7866-.6719-.8147-1.5148-1.4469-2.5294-1.8971-1.0146-.4502-2.1297-.6753-3.3441-.6753-1.4367 0-2.7372.3388-3.9017 1.0181-1.165.6712-2.0797 1.6009-2.7441 2.7866-.6643 1.1788-.9968 2.4972-.9968 3.955 0 1.4579.3325 2.7803.9968 3.966.6649 1.1789 1.5791 2.1073 2.7441 2.7866Z\"/> <path fill=\"#fff\" d=\"M16.9941 44.001h16v3h-16v-3Z\"/> </svg>",
|
|
24
|
+
}
|