dsh-ide-git 0.18.5 → 0.18.6

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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-ide-git",
3
- "version": "0.18.5",
3
+ "version": "0.18.6",
4
4
  "main": "./src/index.js",
5
5
  "description": "IDE 级 Git 操作面板:分支树 + 提交图谱 + 变更/暂存/提交 + 提交详情。装了 dsh-better-sidebar 时挂成它的原生 tab(右侧栏 + 底部工作台);没装时直接挂 DSH 原生右侧栏,单独安装即可用。JetBrains 风格 Git 工具窗口 for DeepSeek Harness.",
6
6
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ide-git",
3
- "version": "0.18.5",
3
+ "version": "0.18.6",
4
4
  "description": "DSH plugin: an IDE-grade Git tool window as a native dsh-better-sidebar tab — branch tree, commit graph, changes, commit details, JetBrains-style actions, in the right sidebar and the bottom panel. | DSH 插件:IDE 级 Git 工具窗口,以 dsh-better-sidebar 原生 Tab 挂载——分支树 / 提交图谱 / 变更与提交详情 / JetBrains 风格操作,右侧栏与底部面板双布局自适应。",
5
5
  "type": "module",
6
6
  "repository": {
package/src/client.js CHANGED
@@ -7762,6 +7762,55 @@ window.__ModuleLoader__.load({
7762
7762
  return index < 0 ? '' : filePath.slice(0, index)
7763
7763
  }
7764
7764
 
7765
+ /* A REAL directory tree for the "by directory" grouping (v0.18.6): files
7766
+ are inserted by path segments, then a folder that has no direct files
7767
+ and exactly one child is COMPRESSED into that child ("a/b/c" as one
7768
+ node, VS Code source-control style) — the flat per-directory heads the
7769
+ user rejected rendered every folder of a chain side by side, so
7770
+ `pkg/service` and `pkg/service/impl` looked like unrelated siblings.
7771
+ Returns the compressed roots (sorted), every foldable node id (for
7772
+ fold-all), the files sitting at the group root, and the two recursive
7773
+ helpers the renderer needs. */
7774
+ function buildDirForest(items, groupKey) {
7775
+ const root = { dirs: new Map(), files: [], path: [] }
7776
+ for (const item of items) {
7777
+ const dir = dirName(item.path)
7778
+ const parts = dir === '' ? [] : dir.split('/')
7779
+ let node = root
7780
+ for (const part of parts) {
7781
+ let child = node.dirs.get(part)
7782
+ if (child === undefined) {
7783
+ child = { dirs: new Map(), files: [], path: node.path.concat([part]) }
7784
+ node.dirs.set(part, child)
7785
+ }
7786
+ node = child
7787
+ }
7788
+ node.files.push(item)
7789
+ }
7790
+ const compress = (node) => {
7791
+ let merged = node
7792
+ while (merged.files.length === 0 && merged.dirs.size === 1) {
7793
+ const only = merged.dirs.values().next().value
7794
+ merged = { dirs: only.dirs, files: only.files, path: only.path }
7795
+ }
7796
+ return merged
7797
+ }
7798
+ const countOf = (node) => {
7799
+ let total = node.files.length
7800
+ for (const child of node.dirs.values()) total += countOf(child)
7801
+ return total
7802
+ }
7803
+ const sortedChildren = (node) => Array.from(node.dirs.values()).sort((a, b) => a.path[a.path.length - 1].localeCompare(b.path[b.path.length - 1]))
7804
+ const ids = []
7805
+ const collect = (node) => {
7806
+ ids.push(groupKey + '|' + node.path.join('/'))
7807
+ for (const child of sortedChildren(node)) collect(child)
7808
+ }
7809
+ const roots = sortedChildren(root).map((child) => compress(child))
7810
+ for (const node of roots) collect(node)
7811
+ return { roots: roots, rootFiles: root.files, ids: ids, countOf: countOf, sortedChildren: sortedChildren }
7812
+ }
7813
+
7765
7814
  /* ============================== commit graph ============================== */
7766
7815
 
7767
7816
  function buildRows(commits) {
@@ -10155,8 +10204,12 @@ window.__ModuleLoader__.load({
10155
10204
  // stage/discard buttons below would silently do nothing (git needs -f).
10156
10205
  const readOnly = props.group === 'ignored'
10157
10206
  const status = readOnly ? 'I' : (raw === '' ? 'M' : raw.charAt(0))
10207
+ /* Tree depth in dir-grouped mode (-1 = flat): every level under a
10208
+ folder head indents one 12px step past the base 26px. */
10209
+ const depth = typeof props.depth === 'number' ? props.depth : -1
10158
10210
  return E('div', {
10159
- className: 'dig-row dig-row-file' + (props.selected === true ? ' dig-row-selected' : '') + (props.nested === true ? ' dig-row-nested' : ''),
10211
+ className: 'dig-row dig-row-file' + (props.selected === true ? ' dig-row-selected' : '') + (depth >= 0 ? ' dig-row-nested' : ''),
10212
+ style: depth >= 0 ? { paddingLeft: (26 + depth * 12) + 'px' } : undefined,
10160
10213
  title: item.origPath === undefined ? item.path : item.origPath + ' → ' + item.path,
10161
10214
  /* The click is handled ONE level up (ChangesPanel): plain click only
10162
10215
  selects, DOUBLE-click opens the diff (v0.18.3 — one interaction model
@@ -10173,14 +10226,14 @@ window.__ModuleLoader__.load({
10173
10226
  // In dir-grouped mode the folder head already names the directory —
10174
10227
  // repeating the full path at the end of every nested row is what made
10175
10228
  // the tree read as a flat list (v0.18.5). Flat modes keep it.
10176
- props.nested === true ? null : E('span', { className: 'dig-row-sub dig-row-dir' }, item.path.slice(-1) === '/' ? '' : dirName(item.path)),
10229
+ depth >= 0 ? null : E('span', { className: 'dig-row-sub dig-row-dir' }, item.path.slice(-1) === '/' ? '' : dirName(item.path)),
10177
10230
  item.additions > 0 ? E('span', { className: 'dig-stat-add' }, '+' + item.additions) : null,
10178
10231
  item.deletions > 0 ? E('span', { className: 'dig-stat-del' }, '-' + item.deletions) : null,
10179
10232
  readOnly ? null : (props.group === 'staged'
10180
10233
  ? E('button', { type: 'button', className: 'dig-mini', title: t('action.unstage'), onClick: (event) => { event.stopPropagation(); props.onUnstage(item) } }, E(Icon, { name: 'minus', size: 12 }))
10181
10234
  : E('button', { type: 'button', className: 'dig-mini', title: t('action.stage'), onClick: (event) => { event.stopPropagation(); props.onStage(item) } }, E(Icon, { name: 'plus', size: 12 }))),
10182
10235
  readOnly ? null : E('button', { type: 'button', className: 'dig-mini', title: t('action.discard'), onClick: (event) => { event.stopPropagation(); props.onDiscard(item, props.group) } }, E(Icon, { name: 'undo', size: 12 })))
10183
- }, (prev, next) => prev.item === next.item && prev.group === next.group && prev.t === next.t && prev.selected === next.selected && prev.nested === next.nested)
10236
+ }, (prev, next) => prev.item === next.item && prev.group === next.group && prev.t === next.t && prev.selected === next.selected && prev.depth === next.depth)
10184
10237
 
10185
10238
  function ChangesPanel(props) {
10186
10239
  const t = props.t
@@ -10416,18 +10469,14 @@ window.__ModuleLoader__.load({
10416
10469
  setAmend(false)
10417
10470
  return true
10418
10471
  }
10419
- const entryRow = (key, item, nested) => E(ChangeRow, {
10472
+ const entryRow = (key, item, depth) => E(ChangeRow, {
10420
10473
  key: key + ':' + item.path, item: item, group: key, t: t,
10421
- nested: nested === true,
10474
+ depth: typeof depth === 'number' ? depth : -1,
10422
10475
  selected: selection.has(key + ':' + item.path) === true,
10423
10476
  onRowClick: handleRowClick,
10424
10477
  onDblClick: props.onDiff,
10425
10478
  onStage: props.onStage, onUnstage: props.onUnstage, onDiscard: props.onDiscard, onDiff: props.onDiff, onMenu: props.onChangeMenu,
10426
10479
  })
10427
- const folderOf = (item) => {
10428
- const dir = dirName(item.path)
10429
- return dir === '' ? t('changes.rootDir') : dir
10430
- }
10431
10480
  /* The "..." menu's sort orders (v0.15). 'git' keeps git's own status
10432
10481
  order; the others are pure client-side sorts of already-loaded
10433
10482
  entries — no extra git calls, no re-fetch. */
@@ -10455,29 +10504,48 @@ window.__ModuleLoader__.load({
10455
10504
  for (const item of sorted) out.push({ kind: 'row', key: key + ':' + item.path, h: ITEM_HEIGHTS.row, node: entryRow(key, item) })
10456
10505
  return
10457
10506
  }
10458
- const folders = new Map()
10459
- for (const item of sorted) {
10460
- const name = folderOf(item)
10461
- if (folders.has(name) === false) folders.set(name, [])
10462
- folders.get(name).push(item)
10463
- }
10464
- for (const name of Array.from(folders.keys()).sort()) {
10465
- const id = key + '|' + name
10507
+ const forest = buildDirForest(sorted, key)
10508
+ const renderFolder = (node, depth) => {
10509
+ const id = key + '|' + node.path.join('/')
10466
10510
  const open = foldedDirs[id] !== true
10467
- const list = folders.get(name)
10468
10511
  out.push({
10469
10512
  kind: 'folder', key: 'folder:' + id, h: ITEM_HEIGHTS.folder,
10470
10513
  node: E('button', {
10471
10514
  type: 'button', className: 'dig-folder-head',
10515
+ style: { paddingLeft: (4 + depth * 12) + 'px' },
10472
10516
  onClick: () => setFoldedDirs((previous) => Object.assign({}, previous, { [id]: previous[id] !== true })),
10473
10517
  },
10474
10518
  E('span', { className: 'dig-chevron' + (open ? ' dig-chevron-open' : '') }, E(Icon, { name: 'chevron', size: 10 })),
10475
10519
  E('span', { className: 'dig-row-icon dig-tone-accent' }, E(Icon, { name: 'folder', size: 10 })),
10476
- E('span', { className: 'dig-folder-name' }, name),
10477
- E('span', { className: 'dig-count' }, String(list.length))),
10520
+ E('span', { className: 'dig-folder-name' }, node.path.join('/')),
10521
+ E('span', { className: 'dig-count' }, String(forest.countOf(node)))),
10522
+ })
10523
+ if (open === true) {
10524
+ /* VS Code order inside a folder: subfolders first (recursively,
10525
+ each indented one more level), then this folder's own files. */
10526
+ for (const child of forest.sortedChildren(node)) renderFolder(child, depth + 1)
10527
+ for (const item of sortedOf(node.files)) out.push({ kind: 'row', key: key + ':' + item.path, h: ITEM_HEIGHTS.row, node: entryRow(key, item, depth) })
10528
+ }
10529
+ }
10530
+ /* Files sitting at the group ROOT get their own (root) head, sorted
10531
+ first — the flat grouping used to give them a localized head too. */
10532
+ if (forest.rootFiles.length > 0) {
10533
+ const rootId = key + '|/'
10534
+ const rootOpen = foldedDirs[rootId] !== true
10535
+ out.push({
10536
+ kind: 'folder', key: 'folder:' + rootId, h: ITEM_HEIGHTS.folder,
10537
+ node: E('button', {
10538
+ type: 'button', className: 'dig-folder-head',
10539
+ onClick: () => setFoldedDirs((previous) => Object.assign({}, previous, { [rootId]: previous[rootId] !== true })),
10540
+ },
10541
+ E('span', { className: 'dig-chevron' + (rootOpen ? ' dig-chevron-open' : '') }, E(Icon, { name: 'chevron', size: 10 })),
10542
+ E('span', { className: 'dig-row-icon dig-tone-accent' }, E(Icon, { name: 'folder', size: 10 })),
10543
+ E('span', { className: 'dig-folder-name' }, t('changes.rootDir')),
10544
+ E('span', { className: 'dig-count' }, String(forest.rootFiles.length))),
10478
10545
  })
10479
- if (open === true) for (const item of list) out.push({ kind: 'row', key: key + ':' + item.path, h: ITEM_HEIGHTS.row, node: entryRow(key, item, true) })
10546
+ if (rootOpen === true) for (const item of sortedOf(forest.rootFiles)) out.push({ kind: 'row', key: key + ':' + item.path, h: ITEM_HEIGHTS.row, node: entryRow(key, item, 0) })
10480
10547
  }
10548
+ for (const node of forest.roots) renderFolder(node, 0)
10481
10549
  }
10482
10550
  const emitGroup = (key, title, entries) => {
10483
10551
  if (entries.length === 0) return
@@ -12600,10 +12668,12 @@ window.__ModuleLoader__.load({
12600
12668
  if (changes !== null) {
12601
12669
  const groups = [['conflicted', changes.conflicted], ['staged', changes.staged], ['unstaged', changes.unstaged], ['untracked', changes.untracked], ['ignored', changes.ignored === undefined ? [] : changes.ignored]]
12602
12670
  for (const pair of groups) {
12603
- for (const item of pair[1]) {
12604
- const dir = dirName(item.path)
12605
- next[pair[0] + '|' + (dir === '' ? t('changes.rootDir') : dir)] = true
12606
- }
12671
+ /* Fold every node of the compressed tree — the ids come from
12672
+ the same builder the renderer uses, so single-chain nodes
12673
+ ("a/b/c") fold exactly where they render (v0.18.6). */
12674
+ const forest = buildDirForest(pair[1], pair[0])
12675
+ if (forest.rootFiles.length > 0) next[pair[0] + '|/'] = true
12676
+ for (const id of forest.ids) next[id] = true
12607
12677
  }
12608
12678
  }
12609
12679
  setFoldedDirs(next)
@@ -13598,7 +13668,8 @@ window.__ModuleLoader__.load({
13598
13668
  '.dig-star-filled svg path{fill:currentColor}',
13599
13669
  // Rows nested under a dir-group folder head are indented and drop the
13600
13670
  // trailing directory repeat, so the grouping reads as a tree.
13601
- '.dig-row-nested{padding-left:26px}',
13671
+ /* Nested rows indent inline (26px base + 12px per tree depth) — the CSS
13672
+ class only marks them for tests/probes. */
13602
13673
  '.dig-graph{flex:none}',
13603
13674
  '.dig-commit-date{color:var(--dsw-alias-label-tertiary);flex:none;width:54px;font-variant-numeric:tabular-nums;font-weight:500}',
13604
13675
  '.dig-commit-author{color:var(--dsw-alias-label-secondary);flex:none;width:96px;overflow:hidden;text-overflow:ellipsis;font-weight:500}',