|
96 | 96 | // ========================================================================== |
97 | 97 |
|
98 | 98 | type DiffMode = 'all' | 'staged' | 'branch' | 'commit' | 'stack'; |
| 99 | + type DiffViewerHandle = { |
| 100 | + flushCommentEditors: () => Promise<boolean>; |
| 101 | + }; |
99 | 102 |
|
100 | 103 | let diffMode = $state<DiffMode>('all'); |
101 | 104 | let diffSpec = $state<DiffSpec>(commands.specUncommitted()); |
|
122 | 125 | let loading = $state(true); |
123 | 126 | let loadingFile = $state<string | null>(null); |
124 | 127 | let error = $state<string | null>(null); |
| 128 | + let diffViewer = $state<DiffViewerHandle | null>(null); |
125 | 129 |
|
126 | 130 | let localComments = $state<Comment[]>([]); |
127 | 131 | let copiedFeedback = $state(false); |
|
255 | 259 | // Diff mode switching |
256 | 260 | // ========================================================================== |
257 | 261 |
|
258 | | - function setMode(mode: DiffMode, commit?: CommitInfo) { |
| 262 | + async function flushCommentEditorsForDiffChange(): Promise<boolean> { |
| 263 | + return (await diffViewer?.flushCommentEditors()) ?? true; |
| 264 | + } |
| 265 | +
|
| 266 | + function resetDiffState() { |
| 267 | + files = []; |
| 268 | + diffCache = new Map(); |
| 269 | + selectedFile = null; |
| 270 | + localComments = []; |
| 271 | + error = null; |
| 272 | + } |
| 273 | +
|
| 274 | + async function setMode(mode: DiffMode, commit?: CommitInfo): Promise<boolean> { |
| 275 | + if (!(await flushCommentEditorsForDiffChange())) return false; |
| 276 | +
|
259 | 277 | showCommitPicker = false; |
260 | 278 | showStackPicker = false; |
261 | 279 | diffMode = mode; |
|
299 | 317 | break; |
300 | 318 | } |
301 | 319 |
|
302 | | - files = []; |
303 | | - diffCache = new Map(); |
304 | | - selectedFile = null; |
305 | | - localComments = []; |
306 | | - error = null; |
| 320 | + resetDiffState(); |
307 | 321 | loadDiff(); |
| 322 | + return true; |
308 | 323 | } |
309 | 324 |
|
310 | | - function selectCommitBySha(sha: string) { |
| 325 | + async function selectCommitBySha(sha: string): Promise<boolean> { |
| 326 | + if (!(await flushCommentEditorsForDiffChange())) return false; |
| 327 | +
|
311 | 328 | diffMode = 'commit'; |
312 | 329 | diffSpec = commands.specCommit(sha); |
313 | 330 | diffLabel = `Commit ${sha.slice(0, 7)}`; |
314 | 331 | selectedCommit = null; |
315 | 332 |
|
316 | | - files = []; |
317 | | - diffCache = new Map(); |
318 | | - selectedFile = null; |
319 | | - localComments = []; |
320 | | - error = null; |
| 333 | + resetDiffState(); |
321 | 334 | loadDiff(); |
| 335 | + return true; |
322 | 336 | } |
323 | 337 |
|
324 | 338 | async function toggleCommitPicker() { |
|
341 | 355 | showStackPicker = !showStackPicker; |
342 | 356 | } |
343 | 357 |
|
344 | | - function selectStackBranch(branch: StackBranchInfo) { |
| 358 | + async function selectStackBranch(branch: StackBranchInfo): Promise<boolean> { |
| 359 | + if (!(await flushCommentEditorsForDiffChange())) return false; |
| 360 | +
|
345 | 361 | stackViewTarget = branch; |
346 | 362 | diffSpec = commands.specStackBranch(branch.name, branch.parentRef); |
347 | 363 | diffLabel = `Stack: ${branch.name.split('/').pop()}`; |
348 | 364 | diffMode = 'stack'; |
349 | 365 | showStackPicker = false; |
350 | 366 |
|
351 | | - files = []; |
352 | | - diffCache = new Map(); |
353 | | - selectedFile = null; |
354 | | - localComments = []; |
355 | | - error = null; |
| 367 | + resetDiffState(); |
356 | 368 | loadDiff(); |
| 369 | + return true; |
357 | 370 | } |
358 | 371 |
|
359 | | - function selectStackCommittedOnly() { |
360 | | - if (!stackInfo) return; |
| 372 | + async function selectStackCommittedOnly(): Promise<boolean> { |
| 373 | + if (!stackInfo) return false; |
| 374 | + if (!(await flushCommentEditorsForDiffChange())) return false; |
| 375 | +
|
361 | 376 | stackViewTarget = null; |
362 | 377 | diffSpec = commands.specStackCommitted(stackInfo.parentBranch); |
363 | 378 | diffLabel = `Stack vs ${stackInfo.parentBranch.split('/').pop()} (committed)`; |
364 | 379 | diffMode = 'stack'; |
365 | 380 | showStackPicker = false; |
366 | 381 |
|
367 | | - files = []; |
368 | | - diffCache = new Map(); |
369 | | - selectedFile = null; |
370 | | - localComments = []; |
371 | | - error = null; |
| 382 | + resetDiffState(); |
372 | 383 | loadDiff(); |
| 384 | + return true; |
373 | 385 | } |
374 | 386 |
|
375 | 387 | // ========================================================================== |
376 | 388 | // Load diff |
377 | 389 | // ========================================================================== |
378 | 390 |
|
379 | 391 | async function loadDiff() { |
| 392 | + if (!(await flushCommentEditorsForDiffChange())) return; |
| 393 | +
|
380 | 394 | loading = true; |
381 | 395 | error = null; |
382 | 396 | try { |
|
394 | 408 | } |
395 | 409 | } |
396 | 410 |
|
397 | | - async function selectFile(path: string | null) { |
| 411 | + async function selectFile(path: string | null): Promise<boolean> { |
| 412 | + if (path === selectedFile) return true; |
| 413 | + if (!(await flushCommentEditorsForDiffChange())) return false; |
| 414 | +
|
398 | 415 | const thisGeneration = ++selectionGeneration; |
399 | 416 |
|
400 | 417 | // Handle search-related behavior (expand and select first result) |
|
408 | 425 | loadingFile = path; |
409 | 426 | try { |
410 | 427 | const diff = await commands.getFileDiff(diffSpec, path); |
411 | | - if (selectionGeneration !== thisGeneration) return; |
| 428 | + if (selectionGeneration !== thisGeneration) return false; |
412 | 429 | const newCache = new Map(diffCache); |
413 | 430 | newCache.set(path, diff); |
414 | 431 | diffCache = newCache; |
|
418 | 435 | loadingFile = null; |
419 | 436 | } |
420 | 437 | } |
| 438 | +
|
| 439 | + return true; |
421 | 440 | } |
422 | 441 |
|
423 | 442 | // ========================================================================== |
|
426 | 445 |
|
427 | 446 | let nextCommentId = 0; |
428 | 447 |
|
429 | | - async function handleAddComment(path: string, span: Span, content: string): Promise<void> { |
| 448 | + async function handleAddComment( |
| 449 | + path: string, |
| 450 | + span: Span, |
| 451 | + content: string |
| 452 | + ): Promise<Comment | null> { |
430 | 453 | const comment: Comment = { |
431 | 454 | id: `local-${++nextCommentId}`, |
432 | 455 | path, |
|
443 | 466 | commitSessionId: null, |
444 | 467 | }; |
445 | 468 | localComments = [...localComments, comment]; |
| 469 | + return comment; |
446 | 470 | } |
447 | 471 |
|
448 | 472 | async function handleUpdateComment(commentId: string, content: string): Promise<void> { |
|
475 | 499 | // ========================================================================== |
476 | 500 |
|
477 | 501 | function handleSelectFile(file: FileEntry) { |
478 | | - selectFile(file.path); |
| 502 | + void selectFile(file.path); |
479 | 503 | } |
480 | 504 |
|
481 | 505 | // Load a file's diff without changing the selection (for search) |
|
514 | 538 | // ========================================================================== |
515 | 539 |
|
516 | 540 | async function handleFolderSelect(path: string) { |
| 541 | + if (!(await flushCommentEditorsForDiffChange())) return; |
| 542 | +
|
517 | 543 | showFolderPicker = false; |
518 | 544 | try { |
519 | 545 | await commands.setRepoPath(path); |
|
527 | 553 | diffMode = 'all'; |
528 | 554 | diffSpec = commands.specUncommitted(); |
529 | 555 | diffLabel = 'All Changes'; |
530 | | - files = []; |
531 | | - diffCache = new Map(); |
532 | | - selectedFile = null; |
533 | | - localComments = []; |
534 | | - error = null; |
| 556 | + resetDiffState(); |
535 | 557 |
|
536 | 558 | // Fetch repo info without triggering a diff load yet — |
537 | 559 | // we need to check for a Graphite stack first to avoid a race |
|
543 | 565 | const si = await commands.getStackInfo(); |
544 | 566 | stackInfo = si; |
545 | 567 | if (si) { |
546 | | - setMode('stack'); |
| 568 | + await setMode('stack'); |
547 | 569 | } else { |
548 | 570 | loadDiff(); |
549 | 571 | } |
|
602 | 624 | } |
603 | 625 |
|
604 | 626 | // Select the file and scroll to the match |
605 | | - await selectFile(filePath); |
| 627 | + if (!(await selectFile(filePath))) return; |
606 | 628 | // Scroll to the specific line |
607 | 629 | lineJumpToken += 1; |
608 | 630 | jumpToLine = { lineIndex: match.lineIndex, token: lineJumpToken }; |
|
936 | 958 | </div> |
937 | 959 | {:else} |
938 | 960 | <DiffViewer |
| 961 | + bind:this={diffViewer} |
939 | 962 | diff={currentDiff} |
940 | 963 | comments={localComments.filter((c) => c.path === selectedFile)} |
941 | 964 | {jumpToLine} |
|
0 commit comments