blob: 650826c5c5b2ec7fc07d9b80ccd964fb077c66c5 [file] [log] [blame]
/* eslint-disable @typescript-eslint/no-explicit-any */
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import "./sketch-monaco-view";
import "./sketch-diff-range-picker";
import "./sketch-diff-empty-view";
import { GitDiffFile, GitDataService } from "./git-data-service";
import { DiffRange } from "./sketch-diff-range-picker";
/**
* A component that displays diffs using Monaco editor with range and file pickers
*/
@customElement("sketch-diff2-view")
export class SketchDiff2View extends LitElement {
/**
* Handles comment events from the Monaco editor and forwards them to the chat input
* using the same event format as the original diff view for consistency.
*/
private handleMonacoComment(event: CustomEvent) {
try {
// Validate incoming data
if (!event.detail || !event.detail.formattedComment) {
console.error("Invalid comment data received");
return;
}
// Create and dispatch event using the standardized format
const commentEvent = new CustomEvent("diff-comment", {
detail: { comment: event.detail.formattedComment },
bubbles: true,
composed: true,
});
this.dispatchEvent(commentEvent);
} catch (error) {
console.error("Error handling Monaco comment:", error);
}
}
/**
* Handle height change events from the Monaco editor
*/
private handleMonacoHeightChange(event: CustomEvent) {
try {
// Get the monaco view that emitted the event
const monacoView = event.target as HTMLElement;
if (!monacoView) return;
// Find the parent file-diff-editor container
const fileDiffEditor = monacoView.closest(
".file-diff-editor",
) as HTMLElement;
if (!fileDiffEditor) return;
// Get the new height from the event
const newHeight = event.detail.height;
// Only update if the height actually changed to avoid unnecessary layout
const currentHeight = fileDiffEditor.style.height;
const newHeightStr = `${newHeight}px`;
if (currentHeight !== newHeightStr) {
// Update the file-diff-editor height to match monaco's height
fileDiffEditor.style.height = newHeightStr;
// Remove any previous min-height constraint that might interfere
fileDiffEditor.style.minHeight = "auto";
// IMPORTANT: Tell Monaco to relayout after its container size changed
// Monaco has automaticLayout: false, so it won't detect container changes
setTimeout(() => {
const monacoComponent = monacoView as any;
if (monacoComponent && monacoComponent.editor) {
// Force layout with explicit dimensions to ensure Monaco fills the space
const editorWidth = fileDiffEditor.offsetWidth;
monacoComponent.editor.layout({
width: editorWidth,
height: newHeight,
});
}
}, 0);
}
} catch (error) {
console.error("Error handling Monaco height change:", error);
}
}
/**
* Handle save events from the Monaco editor
*/
private async handleMonacoSave(event: CustomEvent) {
try {
// Validate incoming data
if (
!event.detail ||
!event.detail.path ||
event.detail.content === undefined
) {
console.error("Invalid save data received");
return;
}
const { path, content } = event.detail;
// Get Monaco view component
const monacoView = this.shadowRoot?.querySelector("sketch-monaco-view");
if (!monacoView) {
console.error("Monaco view not found");
return;
}
try {
await this.gitService?.saveFileContent(path, content);
console.log(`File saved: ${path}`);
(monacoView as any).notifySaveComplete(true);
} catch (error) {
console.error(
`Error saving file: ${error instanceof Error ? error.message : String(error)}`,
);
(monacoView as any).notifySaveComplete(false);
}
} catch (error) {
console.error("Error handling save:", error);
}
}
@property({ type: String })
initialCommit: string = "";
// The commit to show - used when showing a specific commit from timeline
@property({ type: String })
commit: string = "";
@property({ type: String })
selectedFilePath: string = "";
@state()
private files: GitDiffFile[] = [];
@state()
private currentRange: DiffRange = { type: "range", from: "", to: "HEAD" };
@state()
private fileContents: Map<
string,
{ original: string; modified: string; editable: boolean }
> = new Map();
@state()
private fileExpandStates: Map<string, boolean> = new Map();
@state()
private loading: boolean = false;
@state()
private error: string | null = null;
@state()
private selectedFile: string = ""; // Empty string means "All files"
@state()
private viewMode: "all" | "single" = "all";
static styles = css`
:host {
display: flex;
height: 100%;
flex: 1;
flex-direction: column;
min-height: 0; /* Critical for flex child behavior */
overflow: hidden;
position: relative; /* Establish positioning context */
}
.controls {
padding: 8px 16px;
border-bottom: 1px solid var(--border-color, #e0e0e0);
background-color: var(--background-light, #f8f8f8);
flex-shrink: 0; /* Prevent controls from shrinking */
}
.controls-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.range-row {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
}
.file-selector-container {
display: flex;
align-items: center;
gap: 8px;
}
.file-selector {
min-width: 200px;
padding: 8px 12px;
border: 1px solid var(--border-color, #ccc);
border-radius: 4px;
background-color: var(--background-color, #fff);
font-family: var(--font-family, system-ui, sans-serif);
font-size: 14px;
cursor: pointer;
}
.file-selector:focus {
outline: none;
border-color: var(--accent-color, #007acc);
box-shadow: 0 0 0 2px var(--accent-color-light, rgba(0, 122, 204, 0.2));
}
.file-selector:disabled {
background-color: var(--background-disabled, #f5f5f5);
color: var(--text-disabled, #999);
cursor: not-allowed;
}
.spacer {
flex: 1;
}
sketch-diff-range-picker {
flex: 1;
min-width: 400px; /* Ensure minimum width for range picker */
}
.view-toggle-button,
.header-expand-button {
background-color: transparent;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
padding: 6px 8px;
font-size: 14px;
cursor: pointer;
white-space: nowrap;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
min-height: 32px;
}
.view-toggle-button:hover,
.header-expand-button:hover {
background-color: var(--background-hover, #e8e8e8);
}
.diff-container {
flex: 1;
overflow: auto;
display: flex;
flex-direction: column;
min-height: 0;
position: relative;
height: 100%;
}
.diff-content {
flex: 1;
overflow: auto;
min-height: 0;
display: flex;
flex-direction: column;
position: relative;
height: 100%;
}
.multi-file-diff-container {
display: flex;
flex-direction: column;
width: 100%;
min-height: 100%;
}
.file-diff-section {
display: flex;
flex-direction: column;
border-bottom: 3px solid var(--border-color, #e0e0e0);
margin-bottom: 0;
}
.file-diff-section:last-child {
border-bottom: none;
}
.file-header {
background-color: var(--background-light, #f8f8f8);
border-bottom: 1px solid var(--border-color, #e0e0e0);
padding: 8px 16px;
font-family: var(--font-family, system-ui, sans-serif);
font-weight: 500;
font-size: 14px;
color: var(--text-primary-color, #333);
position: sticky;
top: 0;
z-index: 10;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.file-header-left {
display: flex;
align-items: center;
gap: 8px;
}
.file-header-right {
display: flex;
align-items: center;
}
.file-expand-button {
background-color: transparent;
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 4px;
padding: 4px 8px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
min-height: 32px;
}
.file-expand-button:hover {
background-color: var(--background-hover, #e8e8e8);
}
.file-path {
font-family: monospace;
font-weight: normal;
color: var(--text-secondary-color, #666);
}
.file-status {
display: inline-block;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
margin-right: 8px;
}
.file-status.added {
background-color: #d4edda;
color: #155724;
}
.file-status.modified {
background-color: #fff3cd;
color: #856404;
}
.file-status.deleted {
background-color: #f8d7da;
color: #721c24;
}
.file-status.renamed {
background-color: #d1ecf1;
color: #0c5460;
}
.file-changes {
margin-left: 8px;
font-size: 12px;
color: var(--text-secondary-color, #666);
}
.file-diff-editor {
display: flex;
flex-direction: column;
min-height: 200px;
/* Height will be set dynamically by monaco editor */
overflow: visible; /* Ensure content is not clipped */
}
.loading,
.empty-diff {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-family: var(--font-family, system-ui, sans-serif);
}
.empty-diff {
color: var(--text-secondary-color, #666);
font-size: 16px;
text-align: center;
}
.error {
color: var(--error-color, #dc3545);
padding: 16px;
font-family: var(--font-family, system-ui, sans-serif);
}
sketch-monaco-view {
--editor-width: 100%;
--editor-height: 100%;
display: flex;
flex-direction: column;
width: 100%;
min-height: 200px;
/* Ensure Monaco view takes full container space */
flex: 1;
}
/* Single file view styles */
.single-file-view {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.single-file-monaco {
flex: 1;
width: 100%;
height: 100%;
min-height: 0;
}
`;
@property({ attribute: false, type: Object })
gitService!: GitDataService;
// The gitService must be passed from parent to ensure proper dependency injection
constructor() {
super();
console.log("SketchDiff2View initialized");
// Fix for monaco-aria-container positioning and hide scrollbars globally
// Add a global style to ensure proper positioning of aria containers and hide scrollbars
const styleElement = document.createElement("style");
styleElement.textContent = `
.monaco-aria-container {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 1px !important;
height: 1px !important;
overflow: hidden !important;
clip: rect(1px, 1px, 1px, 1px) !important;
white-space: nowrap !important;
margin: 0 !important;
padding: 0 !important;
border: 0 !important;
z-index: -1 !important;
}
/* Aggressively hide all Monaco scrollbar elements */
.monaco-editor .scrollbar,
.monaco-editor .scroll-decoration,
.monaco-editor .invisible.scrollbar,
.monaco-editor .slider,
.monaco-editor .vertical.scrollbar,
.monaco-editor .horizontal.scrollbar,
.monaco-diff-editor .scrollbar,
.monaco-diff-editor .scroll-decoration,
.monaco-diff-editor .invisible.scrollbar,
.monaco-diff-editor .slider,
.monaco-diff-editor .vertical.scrollbar,
.monaco-diff-editor .horizontal.scrollbar {
display: none !important;
visibility: hidden !important;
width: 0 !important;
height: 0 !important;
opacity: 0 !important;
}
/* Target the specific scrollbar classes that Monaco uses */
.monaco-scrollable-element > .scrollbar,
.monaco-scrollable-element > .scroll-decoration,
.monaco-scrollable-element .slider {
display: none !important;
visibility: hidden !important;
width: 0 !important;
height: 0 !important;
}
/* Remove scrollbar space/padding from content area */
.monaco-editor .monaco-scrollable-element,
.monaco-diff-editor .monaco-scrollable-element {
padding-right: 0 !important;
padding-bottom: 0 !important;
margin-right: 0 !important;
margin-bottom: 0 !important;
}
/* Ensure the diff content takes full width without scrollbar space */
.monaco-diff-editor .editor.modified,
.monaco-diff-editor .editor.original {
margin-right: 0 !important;
padding-right: 0 !important;
}
`;
document.head.appendChild(styleElement);
}
connectedCallback() {
super.connectedCallback();
// Initialize with default range and load data
// Get base commit if not set
if (
this.currentRange.type === "range" &&
!("from" in this.currentRange && this.currentRange.from)
) {
this.gitService
.getBaseCommitRef()
.then((baseRef) => {
this.currentRange = { type: "range", from: baseRef, to: "HEAD" };
this.loadDiffData();
})
.catch((error) => {
console.error("Error getting base commit ref:", error);
// Use default range
this.loadDiffData();
});
} else {
this.loadDiffData();
}
}
// Toggle hideUnchangedRegions setting for a specific file
private toggleFileExpansion(filePath: string) {
const currentState = this.fileExpandStates.get(filePath) ?? false;
const newState = !currentState;
this.fileExpandStates.set(filePath, newState);
// Apply to the specific Monaco view component for this file
const monacoView = this.shadowRoot?.querySelector(
`sketch-monaco-view[data-file-path="${filePath}"]`,
);
if (monacoView) {
(monacoView as any).toggleHideUnchangedRegions(!newState); // inverted because true means "hide unchanged"
}
// Force a re-render to update the button state
this.requestUpdate();
}
render() {
return html`
<div class="controls">
<div class="controls-container">
<div class="range-row">
<sketch-diff-range-picker
.gitService="${this.gitService}"
@range-change="${this.handleRangeChange}"
></sketch-diff-range-picker>
<div class="spacer"></div>
${this.renderFileSelector()}
</div>
</div>
</div>
<div class="diff-container">
<div class="diff-content">${this.renderDiffContent()}</div>
</div>
`;
}
renderFileSelector() {
const fileCount = this.files.length;
return html`
<div class="file-selector-container">
<select
class="file-selector"
.value="${this.selectedFile}"
@change="${this.handleFileSelection}"
?disabled="${fileCount === 0}"
>
<option value="">All files (${fileCount})</option>
${this.files.map(
(file) => html`
<option value="${file.path}">
${this.getFileDisplayName(file)}
</option>
`,
)}
</select>
${this.selectedFile ? this.renderSingleFileExpandButton() : ""}
</div>
`;
}
renderDiffContent() {
if (this.loading) {
return html`<div class="loading">Loading diff...</div>`;
}
if (this.error) {
return html`<div class="error">${this.error}</div>`;
}
if (this.files.length === 0) {
return html`<sketch-diff-empty-view></sketch-diff-empty-view>`;
}
// Render single file view if a specific file is selected
if (this.selectedFile && this.viewMode === "single") {
return this.renderSingleFileView();
}
// Render multi-file view
return html`
<div class="multi-file-diff-container">
${this.files.map((file, index) => this.renderFileDiff(file, index))}
</div>
`;
}
/**
* Load diff data for the current range
*/
async loadDiffData() {
this.loading = true;
this.error = null;
try {
// Initialize files as empty array if undefined
if (!this.files) {
this.files = [];
}
// Load diff data for the range
this.files = await this.gitService.getDiff(
this.currentRange.from,
this.currentRange.to,
);
// Ensure files is always an array, even when API returns null
if (!this.files) {
this.files = [];
}
// Load content for all files
if (this.files.length > 0) {
// Initialize expand states for new files (default to collapsed)
this.files.forEach((file) => {
if (!this.fileExpandStates.has(file.path)) {
this.fileExpandStates.set(file.path, false); // false = collapsed (hide unchanged regions)
}
});
await this.loadAllFileContents();
} else {
// No files to display - reset the view to initial state
this.selectedFilePath = "";
this.selectedFile = "";
this.viewMode = "all";
this.fileContents.clear();
this.fileExpandStates.clear();
}
} catch (error) {
console.error("Error loading diff data:", error);
this.error = `Error loading diff data: ${error.message}`;
// Ensure files is an empty array when an error occurs
this.files = [];
// Reset the view to initial state
this.selectedFilePath = "";
this.selectedFile = "";
this.viewMode = "all";
this.fileContents.clear();
this.fileExpandStates.clear();
} finally {
this.loading = false;
}
}
/**
* Load content for all files in the diff
*/
async loadAllFileContents() {
this.loading = true;
this.error = null;
this.fileContents.clear();
try {
let isUnstagedChanges = false;
// Determine the commits to compare based on the current range
const _fromCommit = this.currentRange.from;
const toCommit = this.currentRange.to;
// Check if this is an unstaged changes view
isUnstagedChanges = toCommit === "";
// Load content for all files
const promises = this.files.map(async (file) => {
try {
let originalCode = "";
let modifiedCode = "";
let editable = isUnstagedChanges;
// Load the original code based on file status
if (file.status !== "A") {
// For modified, renamed, or deleted files: load original content
originalCode = await this.gitService.getFileContent(
file.old_hash || "",
);
}
// For modified code, always use working copy when editable
if (editable) {
try {
// Always use working copy when editable, regardless of diff status
modifiedCode = await this.gitService.getWorkingCopyContent(
file.path,
);
} catch (error) {
if (file.status === "D") {
// For deleted files, silently use empty content
console.warn(
`Could not get working copy for deleted file ${file.path}, using empty content`,
);
modifiedCode = "";
} else {
// For any other file status, propagate the error
console.error(
`Failed to get working copy for ${file.path}:`,
error,
);
throw error;
}
}
} else {
// For non-editable view, use git content based on file status
if (file.status === "D") {
// Deleted file: empty modified
modifiedCode = "";
} else {
// Added/modified/renamed: use the content from git
modifiedCode = await this.gitService.getFileContent(
file.new_hash || "",
);
}
}
// Don't make deleted files editable
if (file.status === "D") {
editable = false;
}
this.fileContents.set(file.path, {
original: originalCode,
modified: modifiedCode,
editable,
});
} catch (error) {
console.error(`Error loading content for file ${file.path}:`, error);
// Store empty content for failed files to prevent blocking
this.fileContents.set(file.path, {
original: "",
modified: "",
editable: false,
});
}
});
await Promise.all(promises);
} catch (error) {
console.error("Error loading file contents:", error);
this.error = `Error loading file contents: ${error.message}`;
} finally {
this.loading = false;
}
}
/**
* Handle range change event from the range picker
*/
handleRangeChange(event: CustomEvent) {
const { range } = event.detail;
console.log("Range changed:", range);
this.currentRange = range;
// Load diff data for the new range
this.loadDiffData();
}
/**
* Render a single file diff section
*/
renderFileDiff(file: GitDiffFile, index: number) {
const content = this.fileContents.get(file.path);
if (!content) {
return html`
<div class="file-diff-section">
<div class="file-header">${this.renderFileHeader(file)}</div>
<div class="loading">Loading ${file.path}...</div>
</div>
`;
}
return html`
<div class="file-diff-section">
<div class="file-header">${this.renderFileHeader(file)}</div>
<div class="file-diff-editor">
<sketch-monaco-view
.originalCode="${content.original}"
.modifiedCode="${content.modified}"
.originalFilename="${file.path}"
.modifiedFilename="${file.path}"
?readOnly="${!content.editable}"
?editable-right="${content.editable}"
@monaco-comment="${this.handleMonacoComment}"
@monaco-save="${this.handleMonacoSave}"
@monaco-height-changed="${this.handleMonacoHeightChange}"
data-file-index="${index}"
data-file-path="${file.path}"
></sketch-monaco-view>
</div>
</div>
`;
}
/**
* Render file header with status and path info
*/
renderFileHeader(file: GitDiffFile) {
const statusClass = this.getFileStatusClass(file.status);
const statusText = this.getFileStatusText(file.status);
const changesInfo = this.getChangesInfo(file);
const pathInfo = this.getPathInfo(file);
const isExpanded = this.fileExpandStates.get(file.path) ?? false;
return html`
<div class="file-header-left">
<span class="file-status ${statusClass}">${statusText}</span>
<span class="file-path">${pathInfo}</span>
${changesInfo
? html`<span class="file-changes">${changesInfo}</span>`
: ""}
</div>
<div class="file-header-right">
<button
class="file-expand-button"
@click="${() => this.toggleFileExpansion(file.path)}"
title="${isExpanded
? "Collapse: Hide unchanged regions to focus on changes"
: "Expand: Show all lines including unchanged regions"}"
>
${isExpanded ? this.renderCollapseIcon() : this.renderExpandAllIcon()}
</button>
</div>
`;
}
/**
* Get CSS class for file status
*/
getFileStatusClass(status: string): string {
switch (status.toUpperCase()) {
case "A":
return "added";
case "M":
return "modified";
case "D":
return "deleted";
case "R":
default:
if (status.toUpperCase().startsWith("R")) {
return "renamed";
}
return "modified";
}
}
/**
* Get display text for file status
*/
getFileStatusText(status: string): string {
switch (status.toUpperCase()) {
case "A":
return "Added";
case "M":
return "Modified";
case "D":
return "Deleted";
case "R":
default:
if (status.toUpperCase().startsWith("R")) {
return "Renamed";
}
return "Modified";
}
}
/**
* Get changes information (+/-) for display
*/
getChangesInfo(file: GitDiffFile): string {
const additions = file.additions || 0;
const deletions = file.deletions || 0;
if (additions === 0 && deletions === 0) {
return "";
}
const parts = [];
if (additions > 0) {
parts.push(`+${additions}`);
}
if (deletions > 0) {
parts.push(`-${deletions}`);
}
return `(${parts.join(", ")})`;
}
/**
* Get path information for display, handling renames
*/
getPathInfo(file: GitDiffFile): string {
if (file.old_path && file.old_path !== "") {
// For renames, show old_path → new_path
return `${file.old_path} → ${file.path}`;
}
// For regular files, just show the path
return file.path;
}
/**
* Render expand all icon (dotted line with arrows pointing away)
*/
renderExpandAllIcon() {
return html`
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<!-- Dotted line in the middle -->
<line
x1="2"
y1="8"
x2="14"
y2="8"
stroke="currentColor"
stroke-width="1"
stroke-dasharray="2,1"
/>
<!-- Large arrow pointing up -->
<path d="M8 2 L5 6 L11 6 Z" fill="currentColor" />
<!-- Large arrow pointing down -->
<path d="M8 14 L5 10 L11 10 Z" fill="currentColor" />
</svg>
`;
}
/**
* Render collapse icon (arrows pointing towards dotted line)
*/
renderCollapseIcon() {
return html`
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<!-- Dotted line in the middle -->
<line
x1="2"
y1="8"
x2="14"
y2="8"
stroke="currentColor"
stroke-width="1"
stroke-dasharray="2,1"
/>
<!-- Large arrow pointing down towards line -->
<path d="M8 6 L5 2 L11 2 Z" fill="currentColor" />
<!-- Large arrow pointing up towards line -->
<path d="M8 10 L5 14 L11 14 Z" fill="currentColor" />
</svg>
`;
}
/**
* Handle file selection change from the dropdown
*/
handleFileSelection(event: Event) {
const selectElement = event.target as HTMLSelectElement;
const selectedValue = selectElement.value;
this.selectedFile = selectedValue;
this.viewMode = selectedValue ? "single" : "all";
// Force re-render
this.requestUpdate();
}
/**
* Get display name for file in the selector
*/
getFileDisplayName(file: GitDiffFile): string {
const status = this.getFileStatusText(file.status);
const pathInfo = this.getPathInfo(file);
return `${status}: ${pathInfo}`;
}
/**
* Render expand/collapse button for single file view in header
*/
renderSingleFileExpandButton() {
if (!this.selectedFile) return "";
const isExpanded = this.fileExpandStates.get(this.selectedFile) ?? false;
return html`
<button
class="header-expand-button"
@click="${() => this.toggleFileExpansion(this.selectedFile)}"
title="${isExpanded
? "Collapse: Hide unchanged regions to focus on changes"
: "Expand: Show all lines including unchanged regions"}"
>
${isExpanded ? this.renderCollapseIcon() : this.renderExpandAllIcon()}
</button>
`;
}
/**
* Render single file view with full-screen Monaco editor
*/
renderSingleFileView() {
const selectedFileData = this.files.find(
(f) => f.path === this.selectedFile,
);
if (!selectedFileData) {
return html`<div class="error">Selected file not found</div>`;
}
const content = this.fileContents.get(this.selectedFile);
if (!content) {
return html`<div class="loading">Loading ${this.selectedFile}...</div>`;
}
return html`
<div class="single-file-view">
<sketch-monaco-view
class="single-file-monaco"
.originalCode="${content.original}"
.modifiedCode="${content.modified}"
.originalFilename="${selectedFileData.path}"
.modifiedFilename="${selectedFileData.path}"
?readOnly="${!content.editable}"
?editable-right="${content.editable}"
@monaco-comment="${this.handleMonacoComment}"
@monaco-save="${this.handleMonacoSave}"
data-file-path="${selectedFileData.path}"
></sketch-monaco-view>
</div>
`;
}
/**
* Refresh the diff view by reloading commits and diff data
*
* This is called when the Monaco diff tab is activated to ensure:
* 1. Branch information from git/recentlog is current (branches can change frequently)
* 2. The diff content is synchronized with the latest repository state
* 3. Users always see up-to-date information without manual refresh
*/
refreshDiffView() {
// First refresh the range picker to get updated branch information
const rangePicker = this.shadowRoot?.querySelector(
"sketch-diff-range-picker",
);
if (rangePicker) {
(rangePicker as any).loadCommits();
}
if (this.commit) {
// Convert single commit to range (commit^ to commit)
this.currentRange = {
type: "range",
from: `${this.commit}^`,
to: this.commit,
};
}
// Then reload diff data based on the current range
this.loadDiffData();
}
}
declare global {
interface HTMLElementTagNameMap {
"sketch-diff2-view": SketchDiff2View;
}
}