blob: 7ca33b3e41bd0c5f748a8ad6ba92acd131c59da4 [file] [log] [blame]
Philip Zeyliger272a90e2025-05-16 14:49:51 -07001import { css, html, LitElement } from "lit";
2import { customElement, property, state } from "lit/decorators.js";
3import "./sketch-monaco-view";
4import "./sketch-diff-range-picker";
5import "./sketch-diff-file-picker";
6import "./sketch-diff-empty-view";
Autoformatter8c463622025-05-16 21:54:17 +00007import {
8 GitDiffFile,
9 GitDataService,
10 DefaultGitDataService,
11} from "./git-data-service";
Philip Zeyliger272a90e2025-05-16 14:49:51 -070012import { DiffRange } from "./sketch-diff-range-picker";
13
14/**
15 * A component that displays diffs using Monaco editor with range and file pickers
16 */
17@customElement("sketch-diff2-view")
18export class SketchDiff2View extends LitElement {
19 /**
20 * Handles comment events from the Monaco editor and forwards them to the chat input
21 * using the same event format as the original diff view for consistency.
22 */
23 private handleMonacoComment(event: CustomEvent) {
24 try {
25 // Validate incoming data
26 if (!event.detail || !event.detail.formattedComment) {
Autoformatter8c463622025-05-16 21:54:17 +000027 console.error("Invalid comment data received");
Philip Zeyliger272a90e2025-05-16 14:49:51 -070028 return;
29 }
Autoformatter8c463622025-05-16 21:54:17 +000030
Philip Zeyliger272a90e2025-05-16 14:49:51 -070031 // Create and dispatch event using the standardized format
Autoformatter8c463622025-05-16 21:54:17 +000032 const commentEvent = new CustomEvent("diff-comment", {
Philip Zeyliger272a90e2025-05-16 14:49:51 -070033 detail: { comment: event.detail.formattedComment },
34 bubbles: true,
Autoformatter8c463622025-05-16 21:54:17 +000035 composed: true,
Philip Zeyliger272a90e2025-05-16 14:49:51 -070036 });
Autoformatter8c463622025-05-16 21:54:17 +000037
Philip Zeyliger272a90e2025-05-16 14:49:51 -070038 this.dispatchEvent(commentEvent);
39 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +000040 console.error("Error handling Monaco comment:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -070041 }
42 }
Autoformatter8c463622025-05-16 21:54:17 +000043
Philip Zeyliger272a90e2025-05-16 14:49:51 -070044 /**
45 * Handle save events from the Monaco editor
46 */
47 private async handleMonacoSave(event: CustomEvent) {
48 try {
49 // Validate incoming data
Autoformatter8c463622025-05-16 21:54:17 +000050 if (
51 !event.detail ||
52 !event.detail.path ||
53 event.detail.content === undefined
54 ) {
55 console.error("Invalid save data received");
Philip Zeyliger272a90e2025-05-16 14:49:51 -070056 return;
57 }
Autoformatter8c463622025-05-16 21:54:17 +000058
Philip Zeyliger272a90e2025-05-16 14:49:51 -070059 const { path, content } = event.detail;
Autoformatter8c463622025-05-16 21:54:17 +000060
Philip Zeyliger272a90e2025-05-16 14:49:51 -070061 // Get Monaco view component
Autoformatter8c463622025-05-16 21:54:17 +000062 const monacoView = this.shadowRoot?.querySelector("sketch-monaco-view");
Philip Zeyliger272a90e2025-05-16 14:49:51 -070063 if (!monacoView) {
Autoformatter8c463622025-05-16 21:54:17 +000064 console.error("Monaco view not found");
Philip Zeyliger272a90e2025-05-16 14:49:51 -070065 return;
66 }
Autoformatter8c463622025-05-16 21:54:17 +000067
Philip Zeyliger272a90e2025-05-16 14:49:51 -070068 try {
69 await this.gitService?.saveFileContent(path, content);
70 console.log(`File saved: ${path}`);
71 (monacoView as any).notifySaveComplete(true);
72 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +000073 console.error(
74 `Error saving file: ${error instanceof Error ? error.message : String(error)}`,
75 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -070076 (monacoView as any).notifySaveComplete(false);
77 }
78 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +000079 console.error("Error handling save:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -070080 }
81 }
82 @property({ type: String })
83 initialCommit: string = "";
Autoformatter8c463622025-05-16 21:54:17 +000084
Philip Zeyliger272a90e2025-05-16 14:49:51 -070085 // The commit to show - used when showing a specific commit from timeline
86 @property({ type: String })
87 commit: string = "";
88
89 @property({ type: String })
90 selectedFilePath: string = "";
91
92 @state()
93 private files: GitDiffFile[] = [];
Autoformatter8c463622025-05-16 21:54:17 +000094
Philip Zeyliger272a90e2025-05-16 14:49:51 -070095 @state()
Autoformatter8c463622025-05-16 21:54:17 +000096 private currentRange: DiffRange = { type: "range", from: "", to: "HEAD" };
Philip Zeyliger272a90e2025-05-16 14:49:51 -070097
98 @state()
99 private originalCode: string = "";
100
101 @state()
102 private modifiedCode: string = "";
Autoformatter8c463622025-05-16 21:54:17 +0000103
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700104 @state()
105 private isRightEditable: boolean = false;
106
107 @state()
108 private loading: boolean = false;
109
110 @state()
111 private error: string | null = null;
112
113 static styles = css`
114 :host {
115 display: flex;
116 height: 100%;
117 flex: 1;
118 flex-direction: column;
119 min-height: 0; /* Critical for flex child behavior */
120 overflow: hidden;
121 position: relative; /* Establish positioning context */
122 }
123
124 .controls {
125 padding: 8px 16px;
126 border-bottom: 1px solid var(--border-color, #e0e0e0);
127 background-color: var(--background-light, #f8f8f8);
128 flex-shrink: 0; /* Prevent controls from shrinking */
129 }
Autoformatter8c463622025-05-16 21:54:17 +0000130
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700131 .controls-container {
132 display: flex;
133 flex-direction: column;
134 gap: 12px;
135 }
Autoformatter8c463622025-05-16 21:54:17 +0000136
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700137 .range-row {
138 width: 100%;
139 display: flex;
140 }
Autoformatter8c463622025-05-16 21:54:17 +0000141
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700142 .file-row {
143 width: 100%;
144 display: flex;
145 justify-content: space-between;
146 align-items: center;
147 gap: 10px;
148 }
Autoformatter8c463622025-05-16 21:54:17 +0000149
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700150 sketch-diff-range-picker {
151 width: 100%;
152 }
Autoformatter8c463622025-05-16 21:54:17 +0000153
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700154 sketch-diff-file-picker {
155 flex: 1;
156 }
Autoformatter8c463622025-05-16 21:54:17 +0000157
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700158 .view-toggle-button {
159 background-color: #f0f0f0;
160 border: 1px solid #ccc;
161 border-radius: 4px;
Philip Zeyligere89b3082025-05-29 03:16:06 +0000162 padding: 8px;
163 font-size: 16px;
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700164 cursor: pointer;
165 white-space: nowrap;
166 transition: background-color 0.2s;
Philip Zeyligere89b3082025-05-29 03:16:06 +0000167 display: flex;
168 align-items: center;
169 justify-content: center;
170 min-width: 36px;
171 min-height: 36px;
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700172 }
Autoformatter8c463622025-05-16 21:54:17 +0000173
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700174 .view-toggle-button:hover {
175 background-color: #e0e0e0;
176 }
177
178 .diff-container {
179 flex: 1;
180 overflow: hidden;
181 display: flex;
182 flex-direction: column;
183 min-height: 0; /* Critical for flex child to respect parent height */
184 position: relative; /* Establish positioning context */
185 height: 100%; /* Take full height */
186 }
187
188 .diff-content {
189 flex: 1;
190 overflow: hidden;
191 min-height: 0; /* Required for proper flex behavior */
192 display: flex; /* Required for child to take full height */
193 position: relative; /* Establish positioning context */
194 height: 100%; /* Take full height */
195 }
196
Autoformatter8c463622025-05-16 21:54:17 +0000197 .loading,
198 .empty-diff {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700199 display: flex;
200 align-items: center;
201 justify-content: center;
202 height: 100%;
203 font-family: var(--font-family, system-ui, sans-serif);
204 }
Autoformatter8c463622025-05-16 21:54:17 +0000205
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700206 .empty-diff {
207 color: var(--text-secondary-color, #666);
208 font-size: 16px;
209 text-align: center;
210 }
211
212 .error {
213 color: var(--error-color, #dc3545);
214 padding: 16px;
215 font-family: var(--font-family, system-ui, sans-serif);
216 }
217
218 sketch-monaco-view {
219 --editor-width: 100%;
220 --editor-height: 100%;
221 flex: 1; /* Make Monaco view take full height */
222 display: flex; /* Required for child to take full height */
223 position: absolute; /* Absolute positioning to take full space */
224 top: 0;
225 left: 0;
226 right: 0;
227 bottom: 0;
228 height: 100%; /* Take full height */
Autoformatter8c463622025-05-16 21:54:17 +0000229 width: 100%; /* Take full width */
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700230 }
231 `;
232
233 @property({ attribute: false, type: Object })
234 gitService!: GitDataService;
Autoformatter8c463622025-05-16 21:54:17 +0000235
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700236 // The gitService must be passed from parent to ensure proper dependency injection
237
238 constructor() {
239 super();
Autoformatter8c463622025-05-16 21:54:17 +0000240 console.log("SketchDiff2View initialized");
241
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700242 // Fix for monaco-aria-container positioning
243 // Add a global style to ensure proper positioning of aria containers
Autoformatter8c463622025-05-16 21:54:17 +0000244 const styleElement = document.createElement("style");
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700245 styleElement.textContent = `
246 .monaco-aria-container {
247 position: absolute !important;
248 top: 0 !important;
249 left: 0 !important;
250 width: 1px !important;
251 height: 1px !important;
252 overflow: hidden !important;
253 clip: rect(1px, 1px, 1px, 1px) !important;
254 white-space: nowrap !important;
255 margin: 0 !important;
256 padding: 0 !important;
257 border: 0 !important;
258 z-index: -1 !important;
259 }
260 `;
261 document.head.appendChild(styleElement);
262 }
263
264 connectedCallback() {
265 super.connectedCallback();
266 // Initialize with default range and load data
267 // Get base commit if not set
Autoformatter8c463622025-05-16 21:54:17 +0000268 if (
269 this.currentRange.type === "range" &&
270 !("from" in this.currentRange && this.currentRange.from)
271 ) {
272 this.gitService
273 .getBaseCommitRef()
274 .then((baseRef) => {
275 this.currentRange = { type: "range", from: baseRef, to: "HEAD" };
276 this.loadDiffData();
277 })
278 .catch((error) => {
279 console.error("Error getting base commit ref:", error);
280 // Use default range
281 this.loadDiffData();
282 });
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700283 } else {
284 this.loadDiffData();
285 }
286 }
287
288 // Toggle hideUnchangedRegions setting
289 @state()
290 private hideUnchangedRegionsEnabled: boolean = true;
Autoformatter8c463622025-05-16 21:54:17 +0000291
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700292 // Toggle hideUnchangedRegions setting
293 private toggleHideUnchangedRegions() {
294 this.hideUnchangedRegionsEnabled = !this.hideUnchangedRegionsEnabled;
Autoformatter8c463622025-05-16 21:54:17 +0000295
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700296 // Get the Monaco view component
Autoformatter8c463622025-05-16 21:54:17 +0000297 const monacoView = this.shadowRoot?.querySelector("sketch-monaco-view");
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700298 if (monacoView) {
Autoformatter8c463622025-05-16 21:54:17 +0000299 (monacoView as any).toggleHideUnchangedRegions(
300 this.hideUnchangedRegionsEnabled,
301 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700302 }
303 }
Autoformatter8c463622025-05-16 21:54:17 +0000304
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700305 render() {
306 return html`
307 <div class="controls">
308 <div class="controls-container">
309 <div class="range-row">
310 <sketch-diff-range-picker
311 .gitService="${this.gitService}"
312 @range-change="${this.handleRangeChange}"
313 ></sketch-diff-range-picker>
314 </div>
Autoformatter8c463622025-05-16 21:54:17 +0000315
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700316 <div class="file-row">
317 <sketch-diff-file-picker
318 .files="${this.files}"
319 .selectedPath="${this.selectedFilePath}"
320 @file-selected="${this.handleFileSelected}"
321 ></sketch-diff-file-picker>
Autoformatter8c463622025-05-16 21:54:17 +0000322
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700323 <div style="display: flex; gap: 8px;">
Autoformatter8c463622025-05-16 21:54:17 +0000324 <button
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700325 class="view-toggle-button"
326 @click="${this.toggleHideUnchangedRegions}"
Autoformatter8c463622025-05-16 21:54:17 +0000327 title="${this.hideUnchangedRegionsEnabled
Philip Zeyligere89b3082025-05-29 03:16:06 +0000328 ? "Expand All: Show all lines including unchanged regions"
329 : "Collapse Expanded Lines: Hide unchanged regions to focus on changes"}"
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700330 >
Autoformatter8c463622025-05-16 21:54:17 +0000331 ${this.hideUnchangedRegionsEnabled
Philip Zeyligere89b3082025-05-29 03:16:06 +0000332 ? this.renderExpandAllIcon()
333 : this.renderCollapseIcon()}
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700334 </button>
335 </div>
336 </div>
337 </div>
338 </div>
339
340 <div class="diff-container">
Autoformatter8c463622025-05-16 21:54:17 +0000341 <div class="diff-content">${this.renderDiffContent()}</div>
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700342 </div>
343 `;
344 }
345
346 renderDiffContent() {
347 if (this.loading) {
348 return html`<div class="loading">Loading diff...</div>`;
349 }
350
351 if (this.error) {
352 return html`<div class="error">${this.error}</div>`;
353 }
354
355 if (this.files.length === 0) {
356 return html`<sketch-diff-empty-view></sketch-diff-empty-view>`;
357 }
Autoformatter8c463622025-05-16 21:54:17 +0000358
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700359 if (!this.selectedFilePath) {
360 return html`<div class="loading">Select a file to view diff</div>`;
361 }
362
363 return html`
364 <sketch-monaco-view
365 .originalCode="${this.originalCode}"
366 .modifiedCode="${this.modifiedCode}"
367 .originalFilename="${this.selectedFilePath}"
368 .modifiedFilename="${this.selectedFilePath}"
369 ?readOnly="${!this.isRightEditable}"
370 ?editable-right="${this.isRightEditable}"
371 @monaco-comment="${this.handleMonacoComment}"
372 @monaco-save="${this.handleMonacoSave}"
373 ></sketch-monaco-view>
374 `;
375 }
376
377 /**
378 * Load diff data for the current range
379 */
380 async loadDiffData() {
381 this.loading = true;
382 this.error = null;
383
384 try {
385 // Initialize files as empty array if undefined
386 if (!this.files) {
387 this.files = [];
388 }
389
390 // Load diff data based on the current range type
Autoformatter8c463622025-05-16 21:54:17 +0000391 if (this.currentRange.type === "single") {
392 this.files = await this.gitService.getCommitDiff(
393 this.currentRange.commit,
394 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700395 } else {
Autoformatter8c463622025-05-16 21:54:17 +0000396 this.files = await this.gitService.getDiff(
397 this.currentRange.from,
398 this.currentRange.to,
399 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700400 }
401
402 // Ensure files is always an array, even when API returns null
403 if (!this.files) {
404 this.files = [];
405 }
Autoformatter8c463622025-05-16 21:54:17 +0000406
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700407 // If we have files, select the first one and load its content
408 if (this.files.length > 0) {
409 const firstFile = this.files[0];
410 this.selectedFilePath = firstFile.path;
Autoformatter8c463622025-05-16 21:54:17 +0000411
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700412 // Directly load the file content, especially important when there's only one file
413 // as sometimes the file-selected event might not fire in that case
414 this.loadFileContent(firstFile);
415 } else {
416 // No files to display - reset the view to initial state
Autoformatter8c463622025-05-16 21:54:17 +0000417 this.selectedFilePath = "";
418 this.originalCode = "";
419 this.modifiedCode = "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700420 }
421 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000422 console.error("Error loading diff data:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700423 this.error = `Error loading diff data: ${error.message}`;
424 // Ensure files is an empty array when an error occurs
425 this.files = [];
426 // Reset the view to initial state
Autoformatter8c463622025-05-16 21:54:17 +0000427 this.selectedFilePath = "";
428 this.originalCode = "";
429 this.modifiedCode = "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700430 } finally {
431 this.loading = false;
432 }
433 }
434
435 /**
436 * Load the content of the selected file
437 */
438 async loadFileContent(file: GitDiffFile) {
439 this.loading = true;
440 this.error = null;
441
442 try {
443 let fromCommit: string;
444 let toCommit: string;
445 let isUnstagedChanges = false;
Autoformatter8c463622025-05-16 21:54:17 +0000446
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700447 // Determine the commits to compare based on the current range
Autoformatter8c463622025-05-16 21:54:17 +0000448 if (this.currentRange.type === "single") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700449 fromCommit = `${this.currentRange.commit}^`;
450 toCommit = this.currentRange.commit;
451 } else {
452 fromCommit = this.currentRange.from;
453 toCommit = this.currentRange.to;
454 // Check if this is an unstaged changes view
Autoformatter8c463622025-05-16 21:54:17 +0000455 isUnstagedChanges = toCommit === "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700456 }
457
458 // Set editability based on whether we're showing uncommitted changes
459 this.isRightEditable = isUnstagedChanges;
460
461 // Load the original code based on file status
Autoformatter8c463622025-05-16 21:54:17 +0000462 if (file.status === "A") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700463 // Added file: empty original
Autoformatter8c463622025-05-16 21:54:17 +0000464 this.originalCode = "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700465 } else {
466 // For modified, renamed, or deleted files: load original content
Autoformatter8c463622025-05-16 21:54:17 +0000467 this.originalCode = await this.gitService.getFileContent(
468 file.old_hash || "",
469 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700470 }
Autoformatter8c463622025-05-16 21:54:17 +0000471
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700472 // For modified code, always use working copy when editable
473 if (this.isRightEditable) {
474 try {
475 // Always use working copy when editable, regardless of diff status
476 // This ensures we have the latest content even if the diff hasn't been refreshed
Autoformatter8c463622025-05-16 21:54:17 +0000477 this.modifiedCode = await this.gitService.getWorkingCopyContent(
478 file.path,
479 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700480 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000481 if (file.status === "D") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700482 // For deleted files, silently use empty content
Autoformatter8c463622025-05-16 21:54:17 +0000483 console.warn(
484 `Could not get working copy for deleted file ${file.path}, using empty content`,
485 );
486 this.modifiedCode = "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700487 } else {
488 // For any other file status, propagate the error
Autoformatter8c463622025-05-16 21:54:17 +0000489 console.error(
490 `Failed to get working copy for ${file.path}:`,
491 error,
492 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700493 throw error; // Rethrow to be caught by the outer try/catch
494 }
495 }
496 } else {
497 // For non-editable view, use git content based on file status
Autoformatter8c463622025-05-16 21:54:17 +0000498 if (file.status === "D") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700499 // Deleted file: empty modified
Autoformatter8c463622025-05-16 21:54:17 +0000500 this.modifiedCode = "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700501 } else {
502 // Added/modified/renamed: use the content from git
Autoformatter8c463622025-05-16 21:54:17 +0000503 this.modifiedCode = await this.gitService.getFileContent(
504 file.new_hash || "",
505 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700506 }
507 }
Autoformatter8c463622025-05-16 21:54:17 +0000508
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700509 // Don't make deleted files editable
Autoformatter8c463622025-05-16 21:54:17 +0000510 if (file.status === "D") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700511 this.isRightEditable = false;
512 }
513 } catch (error) {
Autoformatter8c463622025-05-16 21:54:17 +0000514 console.error("Error loading file content:", error);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700515 this.error = `Error loading file content: ${error.message}`;
516 this.isRightEditable = false;
517 } finally {
518 this.loading = false;
519 }
520 }
521
522 /**
523 * Handle range change event from the range picker
524 */
525 handleRangeChange(event: CustomEvent) {
526 const { range } = event.detail;
Autoformatter8c463622025-05-16 21:54:17 +0000527 console.log("Range changed:", range);
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700528 this.currentRange = range;
Autoformatter8c463622025-05-16 21:54:17 +0000529
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700530 // Load diff data for the new range
531 this.loadDiffData();
532 }
533
534 /**
535 * Handle file selection event from the file picker
536 */
537 handleFileSelected(event: CustomEvent) {
538 const file = event.detail.file as GitDiffFile;
539 this.selectedFilePath = file.path;
540 this.loadFileContent(file);
541 }
542
543 /**
Philip Zeyligere89b3082025-05-29 03:16:06 +0000544 * Render expand all icon (dotted line with arrows pointing away)
545 */
546 renderExpandAllIcon() {
547 return html`
548 <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
549 <!-- Dotted line in the middle -->
550 <line
551 x1="2"
552 y1="8"
553 x2="14"
554 y2="8"
555 stroke="currentColor"
556 stroke-width="1"
557 stroke-dasharray="2,1"
558 />
559 <!-- Large arrow pointing up -->
560 <path d="M8 2 L5 6 L11 6 Z" fill="currentColor" />
561 <!-- Large arrow pointing down -->
562 <path d="M8 14 L5 10 L11 10 Z" fill="currentColor" />
563 </svg>
564 `;
565 }
566
567 /**
568 * Render collapse icon (arrows pointing towards dotted line)
569 */
570 renderCollapseIcon() {
571 return html`
572 <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
573 <!-- Dotted line in the middle -->
574 <line
575 x1="2"
576 y1="8"
577 x2="14"
578 y2="8"
579 stroke="currentColor"
580 stroke-width="1"
581 stroke-dasharray="2,1"
582 />
583 <!-- Large arrow pointing down towards line -->
584 <path d="M8 6 L5 2 L11 2 Z" fill="currentColor" />
585 <!-- Large arrow pointing up towards line -->
586 <path d="M8 10 L5 14 L11 14 Z" fill="currentColor" />
587 </svg>
588 `;
589 }
590
591 /**
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700592 * Refresh the diff view by reloading commits and diff data
Autoformatter8c463622025-05-16 21:54:17 +0000593 *
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700594 * This is called when the Monaco diff tab is activated to ensure:
595 * 1. Branch information from git/recentlog is current (branches can change frequently)
596 * 2. The diff content is synchronized with the latest repository state
597 * 3. Users always see up-to-date information without manual refresh
598 */
599 refreshDiffView() {
600 // First refresh the range picker to get updated branch information
Autoformatter8c463622025-05-16 21:54:17 +0000601 const rangePicker = this.shadowRoot?.querySelector(
602 "sketch-diff-range-picker",
603 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700604 if (rangePicker) {
605 (rangePicker as any).loadCommits();
606 }
Autoformatter8c463622025-05-16 21:54:17 +0000607
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700608 if (this.commit) {
Autoformatter8c463622025-05-16 21:54:17 +0000609 this.currentRange = { type: "single", commit: this.commit };
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700610 }
Autoformatter8c463622025-05-16 21:54:17 +0000611
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700612 // Then reload diff data based on the current range
613 this.loadDiffData();
614 }
615}
616
617declare global {
618 interface HTMLElementTagNameMap {
619 "sketch-diff2-view": SketchDiff2View;
620 }
621}