@theia/scm 1.71.0-next.8 → 1.71.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.
Files changed (80) hide show
  1. package/lib/browser/dirty-diff/dirty-diff-widget.js +1 -1
  2. package/lib/browser/dirty-diff/dirty-diff-widget.js.map +1 -1
  3. package/lib/browser/merge-editor/merge-editor-module.d.ts +6 -0
  4. package/lib/browser/merge-editor/merge-editor-module.d.ts.map +1 -1
  5. package/lib/browser/merge-editor/merge-editor-module.js +21 -2
  6. package/lib/browser/merge-editor/merge-editor-module.js.map +1 -1
  7. package/lib/browser/scm-context-key-service.d.ts +8 -0
  8. package/lib/browser/scm-context-key-service.d.ts.map +1 -1
  9. package/lib/browser/scm-context-key-service.js +16 -0
  10. package/lib/browser/scm-context-key-service.js.map +1 -1
  11. package/lib/browser/scm-contribution.d.ts.map +1 -1
  12. package/lib/browser/scm-contribution.js +143 -1
  13. package/lib/browser/scm-contribution.js.map +1 -1
  14. package/lib/browser/scm-frontend-module.d.ts.map +1 -1
  15. package/lib/browser/scm-frontend-module.js +14 -0
  16. package/lib/browser/scm-frontend-module.js.map +1 -1
  17. package/lib/browser/scm-history-graph-helpers.d.ts +39 -0
  18. package/lib/browser/scm-history-graph-helpers.d.ts.map +1 -0
  19. package/lib/browser/scm-history-graph-helpers.js +167 -0
  20. package/lib/browser/scm-history-graph-helpers.js.map +1 -0
  21. package/lib/browser/scm-history-graph-lanes.d.ts +59 -0
  22. package/lib/browser/scm-history-graph-lanes.d.ts.map +1 -0
  23. package/lib/browser/scm-history-graph-lanes.js +183 -0
  24. package/lib/browser/scm-history-graph-lanes.js.map +1 -0
  25. package/lib/browser/scm-history-graph-lanes.spec.d.ts +2 -0
  26. package/lib/browser/scm-history-graph-lanes.spec.d.ts.map +1 -0
  27. package/lib/browser/scm-history-graph-lanes.spec.js +554 -0
  28. package/lib/browser/scm-history-graph-lanes.spec.js.map +1 -0
  29. package/lib/browser/scm-history-graph-model.d.ts +46 -0
  30. package/lib/browser/scm-history-graph-model.d.ts.map +1 -0
  31. package/lib/browser/scm-history-graph-model.js +184 -0
  32. package/lib/browser/scm-history-graph-model.js.map +1 -0
  33. package/lib/browser/scm-history-graph-model.spec.d.ts +2 -0
  34. package/lib/browser/scm-history-graph-model.spec.d.ts.map +1 -0
  35. package/lib/browser/scm-history-graph-model.spec.js +131 -0
  36. package/lib/browser/scm-history-graph-model.spec.js.map +1 -0
  37. package/lib/browser/scm-history-graph-tooltip.d.ts +14 -0
  38. package/lib/browser/scm-history-graph-tooltip.d.ts.map +1 -0
  39. package/lib/browser/scm-history-graph-tooltip.js +190 -0
  40. package/lib/browser/scm-history-graph-tooltip.js.map +1 -0
  41. package/lib/browser/scm-history-graph-widget.d.ts +77 -0
  42. package/lib/browser/scm-history-graph-widget.d.ts.map +1 -0
  43. package/lib/browser/scm-history-graph-widget.js +490 -0
  44. package/lib/browser/scm-history-graph-widget.js.map +1 -0
  45. package/lib/browser/scm-provider.d.ts +61 -0
  46. package/lib/browser/scm-provider.d.ts.map +1 -1
  47. package/lib/browser/scm-provider.js.map +1 -1
  48. package/lib/browser/scm-repositories-widget.d.ts +6 -1
  49. package/lib/browser/scm-repositories-widget.d.ts.map +1 -1
  50. package/lib/browser/scm-repositories-widget.js +11 -4
  51. package/lib/browser/scm-repositories-widget.js.map +1 -1
  52. package/lib/browser/scm-repositories-widget.spec.js +1 -1
  53. package/lib/browser/scm-repositories-widget.spec.js.map +1 -1
  54. package/lib/browser/scm-service.d.ts.map +1 -1
  55. package/lib/browser/scm-service.js +4 -1
  56. package/lib/browser/scm-service.js.map +1 -1
  57. package/lib/browser/scm-service.spec.d.ts +2 -0
  58. package/lib/browser/scm-service.spec.d.ts.map +1 -0
  59. package/lib/browser/scm-service.spec.js +77 -0
  60. package/lib/browser/scm-service.spec.js.map +1 -0
  61. package/package.json +11 -11
  62. package/src/browser/dirty-diff/dirty-diff-widget.ts +1 -1
  63. package/src/browser/merge-editor/merge-editor-module.ts +24 -6
  64. package/src/browser/scm-context-key-service.ts +24 -0
  65. package/src/browser/scm-contribution.ts +157 -0
  66. package/src/browser/scm-frontend-module.ts +15 -0
  67. package/src/browser/scm-history-graph-helpers.ts +175 -0
  68. package/src/browser/scm-history-graph-lanes.spec.ts +635 -0
  69. package/src/browser/scm-history-graph-lanes.ts +258 -0
  70. package/src/browser/scm-history-graph-model.spec.ts +171 -0
  71. package/src/browser/scm-history-graph-model.ts +207 -0
  72. package/src/browser/scm-history-graph-tooltip.ts +213 -0
  73. package/src/browser/scm-history-graph-widget.tsx +712 -0
  74. package/src/browser/scm-provider.ts +68 -0
  75. package/src/browser/scm-repositories-widget.spec.ts +1 -1
  76. package/src/browser/scm-repositories-widget.tsx +10 -3
  77. package/src/browser/scm-service.spec.ts +91 -0
  78. package/src/browser/scm-service.ts +4 -1
  79. package/src/browser/style/index.css +12 -13
  80. package/src/browser/style/scm-history-graph.css +313 -0
@@ -18,11 +18,13 @@
18
18
 
19
19
  import { Disposable, Event } from '@theia/core/lib/common';
20
20
  import URI from '@theia/core/lib/common/uri';
21
+ import { CancellationToken } from '@theia/core/lib/common/cancellation';
21
22
 
22
23
  export interface ScmProvider extends Disposable {
23
24
  readonly id: string;
24
25
  readonly label: string;
25
26
  readonly rootUri: string;
27
+ readonly handle?: number;
26
28
 
27
29
  readonly acceptInputCommand?: ScmCommand;
28
30
 
@@ -41,6 +43,8 @@ export interface ScmProvider extends Disposable {
41
43
  readonly onDidChangeActionButton?: Event<ScmActionButton | undefined>;
42
44
 
43
45
  readonly providerContextValue?: string;
46
+
47
+ readonly historyProvider?: ScmHistoryProvider;
44
48
  }
45
49
 
46
50
  export const ScmResourceGroup = Symbol('ScmResourceGroup');
@@ -102,3 +106,67 @@ export interface ScmActionButton {
102
106
  enabled?: boolean;
103
107
  description?: string;
104
108
  }
109
+
110
+ export interface ScmHistoryItemRef {
111
+ readonly id: string;
112
+ readonly name: string;
113
+ readonly description?: string;
114
+ readonly revision?: string;
115
+ readonly icon?: string;
116
+ readonly category?: string;
117
+ }
118
+
119
+ export interface ScmHistoryItemRefsChangeEvent {
120
+ readonly added: readonly ScmHistoryItemRef[];
121
+ readonly removed: readonly ScmHistoryItemRef[];
122
+ readonly modified: readonly ScmHistoryItemRef[];
123
+ }
124
+
125
+ export interface ScmHistoryOptions {
126
+ readonly skip?: number;
127
+ readonly limit?: number | { id?: string };
128
+ readonly historyItemRefs?: readonly string[];
129
+ readonly filterText?: string;
130
+ }
131
+
132
+ export interface ScmHistoryItemStatistics {
133
+ readonly files: number;
134
+ readonly insertions: number;
135
+ readonly deletions: number;
136
+ }
137
+
138
+ export interface ScmHistoryItem {
139
+ readonly id: string;
140
+ readonly parentIds?: readonly string[];
141
+ readonly subject: string;
142
+ readonly message?: string;
143
+ readonly author?: string;
144
+ readonly authorEmail?: string;
145
+ readonly authorIcon?: string;
146
+ readonly displayId?: string;
147
+ readonly timestamp?: number;
148
+ readonly tooltip?: string;
149
+ readonly statistics?: ScmHistoryItemStatistics;
150
+ readonly references?: readonly ScmHistoryItemRef[];
151
+ }
152
+
153
+ export interface ScmHistoryItemChange {
154
+ readonly uri: string;
155
+ readonly originalUri?: string;
156
+ readonly modifiedUri?: string;
157
+ readonly renameUri?: string;
158
+ }
159
+
160
+ export interface ScmHistoryProvider {
161
+ readonly currentHistoryItemRef?: ScmHistoryItemRef;
162
+ readonly currentHistoryItemRemoteRef?: ScmHistoryItemRef;
163
+ readonly currentHistoryItemBaseRef?: ScmHistoryItemRef;
164
+ readonly onDidChangeCurrentHistoryItemRefs: Event<void>;
165
+ readonly onDidChangeHistoryItemRefs: Event<ScmHistoryItemRefsChangeEvent>;
166
+
167
+ provideHistoryItemRefs(historyItemRefs: string[] | undefined, token: CancellationToken): Promise<ScmHistoryItemRef[] | undefined>;
168
+ provideHistoryItems(options: ScmHistoryOptions, token: CancellationToken): Promise<ScmHistoryItem[] | undefined>;
169
+ provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<ScmHistoryItemChange[] | undefined>;
170
+ resolveHistoryItem(historyItemId: string, token: CancellationToken): Promise<ScmHistoryItem | undefined>;
171
+ resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[], token: CancellationToken): Promise<string | undefined>;
172
+ }
@@ -89,7 +89,7 @@ function createWidget(opts: CreateWidgetOptions): ScmRepositoriesWidget {
89
89
  };
90
90
 
91
91
  const mockContextMenuRenderer = { render: () => ({ dispose: () => { } }) };
92
- const mockScmContextKeys = { scmProvider: { set: () => { } } };
92
+ const mockScmContextKeys = { scmProvider: { set: () => { } }, scmProviderContext: { set: () => { } } };
93
93
 
94
94
  (widget as unknown as Record<string, unknown>).scmService = mockScmService;
95
95
  (widget as unknown as Record<string, unknown>).labelProvider = mockLabelProvider;
@@ -32,10 +32,15 @@ import { ScmContextKeyService } from './scm-context-key-service';
32
32
  export const SCM_TITLE_MENU: MenuPath = ['plugin_scm/title'];
33
33
  /** Menu path matching the VS Code 'scm/sourceControl' contribution point (inline toolbar actions on repo entries). */
34
34
  export const SCM_SOURCE_CONTROL_MENU: MenuPath = ['plugin_scm/sourceControl'];
35
- /** Menu path matching the VS Code 'scm/sourceControl/context' contribution point (context menu on repo entries). */
35
+ /**
36
+ * @deprecated The 'scm/sourceControl/context' contribution point does not exist in VS Code.
37
+ * Use {@link SCM_SOURCE_CONTROL_MENU} instead.
38
+ */
36
39
  export const SCM_SOURCE_CONTROL_CONTEXT_MENU: MenuPath = ['plugin_scm/sourceControl/context'];
37
40
  /** Menu path matching the VS Code 'scm/sourceControl/title' contribution point (REPOSITORIES section header toolbar). */
38
41
  export const SCM_SOURCE_CONTROL_TITLE_MENU: MenuPath = ['plugin_scm/sourceControl/title'];
42
+ /** Menu path matching the VS Code 'scm/repository' contribution point (per-repo \`...\` button). */
43
+ export const SCM_REPOSITORY_MENU: MenuPath = ['plugin_scm/repository'];
39
44
 
40
45
  interface RepoGroup {
41
46
  root: ScmRepository;
@@ -297,11 +302,12 @@ export class ScmRepositoriesWidget extends ReactWidget {
297
302
  // Select the repo and set the context key so command when-clauses resolve correctly.
298
303
  this.scmService.selectedRepository = repo;
299
304
  this.scmContextKeys.scmProvider.set(repo.provider.id);
305
+ this.scmContextKeys.scmProviderContext.set(repo.provider.providerContextValue);
300
306
  const anchor = e.nativeEvent;
301
307
  // Defer one tick so the selectedRepository change propagates through context keys.
302
308
  setTimeout(() => {
303
309
  this.contextMenuRenderer.render({
304
- menuPath: SCM_TITLE_MENU,
310
+ menuPath: SCM_REPOSITORY_MENU,
305
311
  anchor,
306
312
  args: [repo],
307
313
  context: this.node
@@ -312,10 +318,11 @@ export class ScmRepositoriesWidget extends ReactWidget {
312
318
  protected showSourceControlContextMenu(e: React.MouseEvent, repo: ScmRepository): void {
313
319
  this.scmService.selectedRepository = repo;
314
320
  this.scmContextKeys.scmProvider.set(repo.provider.id);
321
+ this.scmContextKeys.scmProviderContext.set(repo.provider.providerContextValue);
315
322
  const anchor = e.nativeEvent;
316
323
  setTimeout(() => {
317
324
  this.contextMenuRenderer.render({
318
- menuPath: SCM_SOURCE_CONTROL_CONTEXT_MENU,
325
+ menuPath: SCM_SOURCE_CONTROL_MENU,
319
326
  anchor,
320
327
  args: [repo],
321
328
  context: this.node
@@ -0,0 +1,91 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource GmbH and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { expect } from 'chai';
18
+ import { Emitter } from '@theia/core/lib/common/event';
19
+ import { ScmService } from './scm-service';
20
+ import { ScmProvider } from './scm-provider';
21
+
22
+ function makeProvider(id: string, rootUri: string): ScmProvider {
23
+ return {
24
+ id,
25
+ label: id,
26
+ rootUri,
27
+ groups: [],
28
+ onDidChange: new Emitter<void>().event,
29
+ onDidChangeCommitTemplate: new Emitter<string>().event,
30
+ dispose: () => { }
31
+ } as unknown as ScmProvider;
32
+ }
33
+
34
+ describe('ScmService - repository removal event ordering', () => {
35
+ let service: ScmService;
36
+
37
+ beforeEach(() => {
38
+ service = new ScmService();
39
+ // ScmService declares a ScmContextKeyService injection but does not use it
40
+ // in the paths exercised here. Stub it to stay resilient against future use.
41
+ (service as unknown as Record<string, unknown>).contextKeys = {};
42
+ });
43
+
44
+ it('should update selectedRepository before firing onDidRemoveRepository', () => {
45
+ const repoA = service.registerScmProvider(makeProvider('git', '/a'));
46
+ const repoB = service.registerScmProvider(makeProvider('git', '/b'));
47
+
48
+ // Select repoA explicitly (registerScmProvider auto-selects the first one).
49
+ service.selectedRepository = repoA;
50
+ expect(service.selectedRepository).to.equal(repoA);
51
+
52
+ let selectedDuringRemoveEvent: unknown;
53
+ service.onDidRemoveRepository(() => {
54
+ selectedDuringRemoveEvent = service.selectedRepository;
55
+ });
56
+
57
+ repoA.dispose();
58
+
59
+ // When a subscriber of onDidRemoveRepository inspects selectedRepository,
60
+ // it must not observe the disposed repository as still being selected.
61
+ expect(selectedDuringRemoveEvent).to.not.equal(repoA);
62
+ expect(selectedDuringRemoveEvent).to.equal(repoB);
63
+ expect(service.selectedRepository).to.equal(repoB);
64
+ });
65
+
66
+ it('should clear selectedRepository to undefined when the last repository is removed', () => {
67
+ const repo = service.registerScmProvider(makeProvider('git', '/a'));
68
+ expect(service.selectedRepository).to.equal(repo);
69
+
70
+ let selectedDuringRemoveEvent: unknown = 'unset';
71
+ service.onDidRemoveRepository(() => {
72
+ selectedDuringRemoveEvent = service.selectedRepository;
73
+ });
74
+
75
+ repo.dispose();
76
+
77
+ expect(selectedDuringRemoveEvent).to.be.undefined;
78
+ expect(service.selectedRepository).to.be.undefined;
79
+ });
80
+
81
+ it('should fire onDidRemoveRepository and leave _repositories empty after disposing the only repo', () => {
82
+ const repo = service.registerScmProvider(makeProvider('git', '/a'));
83
+ let removed: unknown;
84
+ service.onDidRemoveRepository(r => { removed = r; });
85
+
86
+ repo.dispose();
87
+
88
+ expect(removed).to.equal(repo);
89
+ expect(service.repositories).to.have.length(0);
90
+ });
91
+ });
@@ -92,10 +92,13 @@ export class ScmService {
92
92
  repository.dispose = () => {
93
93
  this._repositories.delete(key);
94
94
  dispose.bind(repository)();
95
- this.onDidRemoveRepositoryEmitter.fire(repository);
95
+ // Update the selected repository before firing the remove event so
96
+ // subscribers do not observe a stale selection pointing at the just
97
+ // disposed repository.
96
98
  if (this._selectedRepository === repository) {
97
99
  this.selectedRepository = this._repositories.values().next().value;
98
100
  }
101
+ this.onDidRemoveRepositoryEmitter.fire(repository);
99
102
  };
100
103
  this._repositories.set(key, repository);
101
104
  this.onDidAddRepositoryEmitter.fire(repository);
@@ -14,15 +14,14 @@
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
16
 
17
+ @import './scm-history-graph.css';
18
+
17
19
  .theia-scm-commit {
18
20
  overflow: hidden;
19
21
  font-size: var(--theia-ui-font-size1);
20
22
  max-height: calc(100% - var(--theia-border-width));
21
23
  position: relative;
22
- padding: var(--theia-ui-padding)
23
- max(var(--theia-ui-padding), var(--theia-scrollbar-width))
24
- var(--theia-ui-padding)
25
- calc(var(--theia-ui-padding) * 3);
24
+ padding: var(--theia-ui-padding) max(var(--theia-ui-padding), var(--theia-scrollbar-width)) var(--theia-ui-padding) calc(var(--theia-ui-padding) * 3);
26
25
  }
27
26
 
28
27
  .theia-scm {
@@ -141,9 +140,9 @@
141
140
  .theia-scm-validation-message-info {
142
141
  background-color: var(--theia-inputValidation-infoBackground) !important;
143
142
  color: var(--theia-inputValidation-infoForeground);
144
- border: var(--theia-border-width) solid
145
- var(--theia-inputValidation-infoBorder);
146
- border-top: none; /* remove top border since the input declares it already */
143
+ border: var(--theia-border-width) solid var(--theia-inputValidation-infoBorder);
144
+ border-top: none;
145
+ /* remove top border since the input declares it already */
147
146
  }
148
147
 
149
148
  .theia-scm-validation-message-success {
@@ -155,17 +154,17 @@
155
154
  .theia-scm-validation-message-warning {
156
155
  background-color: var(--theia-inputValidation-warningBackground) !important;
157
156
  color: var(--theia-inputValidation-warningForeground);
158
- border: var(--theia-border-width) solid
159
- var(--theia-inputValidation-warningBorder);
160
- border-top: none; /* remove top border since the input declares it already */
157
+ border: var(--theia-border-width) solid var(--theia-inputValidation-warningBorder);
158
+ border-top: none;
159
+ /* remove top border since the input declares it already */
161
160
  }
162
161
 
163
162
  .theia-scm-validation-message-error {
164
163
  background-color: var(--theia-inputValidation-errorBackground) !important;
165
164
  color: var(--theia-inputValidation-errorForeground);
166
- border: var(--theia-border-width) solid
167
- var(--theia-inputValidation-errorBorder);
168
- border-top: none; /* remove top border since the input declares it already */
165
+ border: var(--theia-border-width) solid var(--theia-inputValidation-errorBorder);
166
+ border-top: none;
167
+ /* remove top border since the input declares it already */
169
168
  }
170
169
 
171
170
  .theia-scm-action-button-container {
@@ -0,0 +1,313 @@
1
+ /********************************************************************************
2
+ * Copyright (C) 2026 EclipseSource GmbH and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ /* ── Widget container ────────────────────────────────────────────────────── */
18
+ .scm-history-graph-container {
19
+ display: flex;
20
+ flex-direction: column;
21
+ height: 100%;
22
+ overflow: hidden;
23
+ font-size: var(--theia-ui-font-size1);
24
+ }
25
+
26
+ /* ── Scrollable list area ────────────────────────────────────────────────── */
27
+ .scm-history-graph-list {
28
+ flex: 1;
29
+ overflow-y: auto;
30
+ overflow-x: hidden;
31
+ }
32
+
33
+ /* ── Individual commit row ───────────────────────────────────────────────── */
34
+ .scm-history-graph-row {
35
+ display: flex;
36
+ align-items: flex-start;
37
+ height: 22px;
38
+ cursor: pointer;
39
+ padding-right: var(--theia-ui-padding);
40
+ box-sizing: border-box;
41
+ overflow: hidden;
42
+ }
43
+
44
+ .scm-history-graph-row:hover {
45
+ background-color: var(--theia-list-hoverBackground);
46
+ color: var(--theia-list-hoverForeground);
47
+ }
48
+
49
+ .scm-history-graph-row.selected {
50
+ background-color: var(--theia-list-activeSelectionBackground);
51
+ color: var(--theia-list-activeSelectionForeground);
52
+ }
53
+
54
+ /* ── SVG lane column ─────────────────────────────────────────────────────── */
55
+ .scm-history-graph-svg {
56
+ flex-shrink: 0;
57
+ overflow: visible;
58
+ display: block;
59
+ }
60
+
61
+ /* ── Commit info column ──────────────────────────────────────────────────── */
62
+ .scm-history-graph-info {
63
+ flex: 1;
64
+ display: flex;
65
+ align-items: center;
66
+ min-width: 0;
67
+ gap: 4px;
68
+ padding-left: 4px;
69
+ overflow: hidden;
70
+ height: 22px;
71
+ }
72
+
73
+ /* ── Subject line (primary content) ─────────────────────────────────────── */
74
+ .scm-history-subject {
75
+ flex-shrink: 1;
76
+ min-width: 0;
77
+ overflow: hidden;
78
+ text-overflow: ellipsis;
79
+ white-space: nowrap;
80
+ font-weight: normal;
81
+ }
82
+
83
+ /* ── Author name (dimmed, next to subject) ───────────────────────────────── */
84
+ .scm-history-author {
85
+ flex-shrink: 1;
86
+ min-width: 0;
87
+ font-size: var(--theia-ui-font-size0);
88
+ opacity: 0.7;
89
+ white-space: nowrap;
90
+ overflow: hidden;
91
+ text-overflow: ellipsis;
92
+ padding-left: 4px;
93
+ }
94
+
95
+ /* ── Badges container (pushed to the right) ─────────────────────────────── */
96
+ .scm-history-badges {
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 2px;
100
+ flex-shrink: 0;
101
+ padding-left: 4px;
102
+ }
103
+
104
+ .scm-history-badges-right {
105
+ margin-left: auto;
106
+ }
107
+
108
+ /* ── Ref badges (branch / tag labels) ───────────────────────────────────── */
109
+ .scm-history-ref-badge {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ gap: 3px;
113
+ padding: 0 6px;
114
+ border-radius: 10px;
115
+ font-size: var(--theia-ui-font-size0);
116
+ line-height: 1;
117
+ min-height: 15px;
118
+ white-space: nowrap;
119
+ flex-shrink: 0;
120
+ max-width: 140px;
121
+ overflow: hidden;
122
+ box-sizing: border-box;
123
+ }
124
+
125
+ .scm-history-ref-badge .scm-history-ref-text {
126
+ overflow: hidden;
127
+ text-overflow: ellipsis;
128
+ min-width: 0;
129
+ }
130
+
131
+ .scm-history-ref-badge .scm-history-ref-icon {
132
+ flex-shrink: 0;
133
+ font-size: 11px;
134
+ line-height: 1;
135
+ }
136
+
137
+ /* Tooltip badges always show text, override max-width */
138
+ .scm-history-ref-badge.tooltip-badge {
139
+ max-width: none;
140
+ }
141
+
142
+ /* Fallback colors when no inline lane color is set */
143
+ .scm-history-ref-badge.head {
144
+ background-color: var(--theia-scmGraph-historyItemRefColor, var(--theia-badge-background));
145
+ color: var(--theia-badge-foreground);
146
+ }
147
+
148
+ .scm-history-ref-badge.remote {
149
+ background-color: var(--theia-scmGraph-historyItemRemoteRefColor, var(--theia-statusBar-background));
150
+ color: var(--theia-statusBar-foreground);
151
+ }
152
+
153
+ .scm-history-ref-badge.tag {
154
+ background-color: var(--theia-scmGraph-historyItemBaseRefColor, var(--theia-inputValidation-warningBackground));
155
+ color: var(--theia-inputValidation-warningForeground);
156
+ }
157
+
158
+ .scm-history-ref-badge.base {
159
+ background-color: var(--theia-list-inactiveSelectionBackground);
160
+ color: var(--theia-list-inactiveSelectionForeground);
161
+ }
162
+
163
+ /* Icon-only cloud badge (remote tracking indicator) */
164
+ .scm-history-ref-badge-cloud {
165
+ padding: 0 4px;
166
+ max-width: none;
167
+ }
168
+
169
+ /* ── Changed files rows (expanded below a commit) ───────────────────────── */
170
+ .scm-history-change-row {
171
+ display: flex;
172
+ align-items: center;
173
+ height: 22px;
174
+ box-sizing: border-box;
175
+ padding-right: var(--theia-ui-padding);
176
+ cursor: pointer;
177
+ }
178
+
179
+ .scm-history-change-row:hover {
180
+ background-color: var(--theia-list-hoverBackground);
181
+ color: var(--theia-list-hoverForeground);
182
+ }
183
+
184
+ .scm-history-change-row.selected {
185
+ background-color: var(--theia-list-activeSelectionBackground);
186
+ color: var(--theia-list-activeSelectionForeground);
187
+ }
188
+
189
+ .scm-history-change-info {
190
+ flex: 1;
191
+ display: flex;
192
+ align-items: center;
193
+ min-width: 0;
194
+ gap: 4px;
195
+ padding-left: 4px;
196
+ overflow: hidden;
197
+ }
198
+
199
+ .scm-history-change-file-icon {
200
+ flex-shrink: 0;
201
+ }
202
+
203
+ .scm-history-change-name-container {
204
+ flex: 1;
205
+ display: flex;
206
+ align-items: center;
207
+ min-width: 0;
208
+ overflow: hidden;
209
+ }
210
+
211
+ .scm-history-change-name {
212
+ flex-shrink: 0;
213
+ overflow: hidden;
214
+ text-overflow: ellipsis;
215
+ white-space: nowrap;
216
+ }
217
+
218
+ .scm-history-change-dir {
219
+ flex: 1;
220
+ font-size: var(--theia-ui-font-size0);
221
+ opacity: 0.6;
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ white-space: nowrap;
225
+ padding-left: 4px;
226
+ }
227
+
228
+ .scm-history-change-status {
229
+ flex-shrink: 0;
230
+ font-size: var(--theia-ui-font-size0);
231
+ font-weight: bold;
232
+ width: 14px;
233
+ text-align: center;
234
+ margin-left: auto;
235
+ }
236
+
237
+ .scm-history-change-status.added {
238
+ color: var(--theia-gitDecoration-addedResourceForeground, #388a34);
239
+ }
240
+
241
+ .scm-history-change-status.deleted {
242
+ color: var(--theia-gitDecoration-deletedResourceForeground, #a1260d);
243
+ }
244
+
245
+ .scm-history-change-status.modified {
246
+ color: var(--theia-gitDecoration-modifiedResourceForeground, #0078d4);
247
+ }
248
+
249
+ .scm-history-change-status.renamed {
250
+ color: var(--theia-gitDecoration-renamedResourceForeground, #b180d7);
251
+ }
252
+
253
+ .scm-history-changes-loading,
254
+ .scm-history-changes-empty {
255
+ display: flex;
256
+ align-items: center;
257
+ height: 22px;
258
+ padding-left: 24px;
259
+ font-size: var(--theia-ui-font-size0);
260
+ opacity: 0.6;
261
+ }
262
+
263
+ /* ── Load-more button ────────────────────────────────────────────────────── */
264
+ .scm-history-load-more {
265
+ display: flex;
266
+ align-items: center;
267
+ justify-content: center;
268
+ height: 28px;
269
+ cursor: pointer;
270
+ font-size: var(--theia-ui-font-size1);
271
+ color: var(--theia-textLink-foreground);
272
+ flex-shrink: 0;
273
+ }
274
+
275
+ .scm-history-load-more:hover {
276
+ text-decoration: underline;
277
+ }
278
+
279
+ /* ── Loading spinner placeholder ────────────────────────────────────────── */
280
+ .scm-history-loading {
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ height: 28px;
285
+ font-size: var(--theia-ui-font-size1);
286
+ opacity: 0.6;
287
+ }
288
+
289
+ /* ── Tooltip header icon alignment ──────────────────────────────────────── */
290
+ .scm-history-tooltip-header .icon-inline {
291
+ vertical-align: middle;
292
+ }
293
+
294
+ /* ── Tooltip stat colors (used inside .theia-hover) ─────────────────────── */
295
+ .scm-history-stat-added {
296
+ color: var(--theia-gitDecoration-addedResourceForeground, #388a34);
297
+ }
298
+
299
+ .scm-history-stat-deleted {
300
+ color: var(--theia-gitDecoration-deletedResourceForeground, #a1260d);
301
+ }
302
+
303
+ /* ── Empty state ─────────────────────────────────────────────────────────── */
304
+ .scm-history-empty {
305
+ display: flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ flex: 1;
309
+ opacity: 0.6;
310
+ font-size: var(--theia-ui-font-size1);
311
+ padding: var(--theia-ui-padding);
312
+ text-align: center;
313
+ }