Skip to content

Preview panel shows whole commit diff instead of selected file's diff when terminal regains focus#390

Merged
idursun merged 1 commit intoidursun:mainfrom
abourget:main
Dec 6, 2025
Merged

Preview panel shows whole commit diff instead of selected file's diff when terminal regains focus#390
idursun merged 1 commit intoidursun:mainfrom
abourget:main

Conversation

@abourget
Copy link
Copy Markdown
Contributor

When the terminal loses focus and regains it, the preview panel refreshes but displays the entire commit diff instead of the diff for the previously selected file. The file selection in the details list is preserved correctly, but the preview content is not in sync.

Root Cause:

Multiple components overwrite each other's selections during refresh operations. When tea.FocusMsg triggers RefreshMsg{KeepSelections: true}, both the revisions and details components process the refresh, but the revisions component calls updateSelection() which overwrites the file selection with a revision selection, causing the preview to use RevisionCommand instead of FileCommand.

Solution:

  1. Preview panel: Only refresh on RefreshMsg if we have a SelectedFile in context, preventing whole-diff display
  2. Revisions component: Add keepSelections flag to skip updateSelection() calls when KeepSelections: true
  3. Details component: Preserve cursor position (highlighted file) during refresh instead of defaulting to first file

This ensures that when the terminal regains focus, the file selection and preview content remain consistent, showing the correct file diff instead of the whole commit diff.


I'm relatively new to the codebase, let me know if this is the wrong way to approve the problem.

@idursun
Copy link
Copy Markdown
Owner

idursun commented Nov 26, 2025

Hey, thanks for the contribution!

Let me have a look at what's really happening.

Copy link
Copy Markdown
Collaborator

@baggiiiie baggiiiie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the fix!

i looked through relevant files and found this fix a bit complicated than it should have been

i think the root cause is:
during refresh, context.SelectedItem is set to SelectedRevision, which is commit level rather than file level when there's files selected

func (m *Model) updateSelection() tea.Cmd {
if selectedRevision := m.SelectedRevision(); selectedRevision != nil {
return m.context.SetSelectedItem(appContext.SelectedRevision{
ChangeId: selectedRevision.GetChangeId(),
CommitId: selectedRevision.CommitId,
})
}
return nil

i think something like this could be done:

func (m *Model) updateSelection() tea.Cmd {
	// Don't override file-level selections (from Details panel)
	if _, isFile := m.context.SelectedItem.(appContext.SelectedFile); isFile {
		return nil
	}
	if selectedRevision := m.SelectedRevision(); selectedRevision != nil {
		return m.context.SetSelectedItem(appContext.SelectedRevision{
			ChangeId: selectedRevision.GetChangeId(),
			CommitId: selectedRevision.CommitId,
		})
	}
	return nil
}

and preview panel is receiving the wrong context here

case context.SelectedFile:
args = jj.TemplatedArgs(config.Current.Preview.FileCommand, map[string]string{
jj.RevsetPlaceholder: m.context.CurrentRevset,
jj.ChangeIdPlaceholder: msg.ChangeId,
jj.CommitIdPlaceholder: msg.CommitId,
jj.FilePlaceholder: msg.File,
})
case context.SelectedRevision:
args = jj.TemplatedArgs(config.Current.Preview.RevisionCommand, map[string]string{
jj.RevsetPlaceholder: m.context.CurrentRevset,
jj.ChangeIdPlaceholder: msg.ChangeId,
jj.CommitIdPlaceholder: msg.CommitId,
})

return s.load(s.revision.GetChangeId())
}

func (s *Operation) preserveSelection(items []*item, currentHighlightedFile string) (context.SelectedItem, int) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im not sure if a whole function of preserveSelection is needed, since there's context.AddCheckedItem and setItem

baggiiiie added a commit to baggiiiie/jjui that referenced this pull request Nov 27, 2025
@baggiiiie
Copy link
Copy Markdown
Collaborator

@abourget i am hopelessly bad to express code with words, just pushed a commit to demo my ideas

it addresses the immediate issue (though im not sure if any edge cases are missing), feel free to have a look and play around and apply to your fix!

@idursun
Copy link
Copy Markdown
Owner

idursun commented Nov 27, 2025

I found out the underlying issue is, when refreshed, revisions send and updateSelection command which is causing the preview window to update its contents.

I have verified that details is actually keeping the cursor at where it should be. (same for evolog as it is also affected by this).

The fix should be that the revisions should run updateSelection if currently selected item is not a revisions. This should be enough to fix the details view problem but not the evolog problem (as it is working with RevisionItem's because evolog item is just the same change id with a different commit id). Having said that though, I see evolog fix can be done in a separate PR.

I agree with @baggiiiie points as the fix is a little bit more convoluted then it should be. I haven't done any work on this, so I don't know yet how feasible the approach I described above, but I feel that it should be straightforward for fixing the details view issue.

…'s diff when terminal regains focus

When the terminal loses focus and regains it, the preview panel refreshes but displays the entire commit diff instead of the diff for the previously selected file. The file selection in the details list is preserved correctly, but the preview content is not in sync.
Root Cause:
Multiple components overwrite each other's selections during refresh operations. When tea.FocusMsg triggers RefreshMsg{KeepSelections: true}, both the revisions and details components process the refresh, but the revisions component calls updateSelection() which overwrites the file selection with a revision selection, causing the preview to use RevisionCommand instead of FileCommand.
Solution:
1. Preview panel: Only refresh on RefreshMsg if we have a SelectedFile in context, preventing whole-diff display
2. Revisions component: Add keepSelections flag to skip updateSelection() calls when KeepSelections: true
3. Details component: Preserve cursor position (highlighted file) during refresh instead of defaulting to first file
This ensures that when the terminal regains focus, the file selection and preview content remain consistent, showing the correct file diff instead of the whole commit diff.

Address feedback
@abourget
Copy link
Copy Markdown
Contributor Author

abourget commented Dec 5, 2025

ok, I've rebased and pushed a new proposition, taking your feedback into account. What do you think?

This one keeps the current line height too, so better than the previous too.

Copy link
Copy Markdown
Owner

@idursun idursun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is working as expected and the changes made are minimal and targeted.

I am happy to merge this to fix the current behaviour but I plan to clean up how this selection tracking is done in a later change.

@idursun idursun merged commit d3a8f72 into idursun:main Dec 6, 2025
3 checks passed
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Dec 12, 2025
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [idursun/jjui](https://github.com/idursun/jjui) | patch | `v0.9.7` -> `v0.9.8` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>idursun/jjui (idursun/jjui)</summary>

### [`v0.9.8`](https://github.com/idursun/jjui/releases/tag/v0.9.8)

[Compare Source](idursun/jjui@v0.9.7...v0.9.8)

#### Release Summary

This release includes experimental Lua scripting support for custom commands, several bug fixes, and UI improvements. The streaming command handler has been reworked to remove the 100ms delay incurred on every refresh (you should feel the difference), and issues with leader keys, parser colour handling, and preview panel focus have been resolved.

##### Key Highlights

**🚀 Major Features**

- **Lua Scripting in Custom Commands ([#&#8203;415](idursun/jjui#415: Experimental support for writing custom commands using Lua scripts.

Initial version includes API for revision navigation, JJ command execution, clipboard operations, revset manipulation and displaying flash messages.

These are the currently available functions but expect the list to grow and change with each release.

**Available Functions (v1)**:

- `revisions.current()` - Get currently selected change ID
- `revisions.checked()` - Get list of checked change IDs
- `revisions.refresh({keep_selections?, selected_revision?})` - Refresh revisions view
- `revisions.navigate({by?, page?, target?, to?, fallback?, ensureView?, allowStream?})` - Navigate revisions
- `revisions.start_squash({files?})` - Begin squash workflow
- `revisions.start_rebase({source?, target?})` - Start rebase operation
- `revisions.open_details()` - Open revision details view
- `revisions.start_inline_describe()` - Open inline describe editor
- `revset.set(value)` - Set custom revset
- `revset.reset()` - Reset to default revset
- `revset.current()` - Get active revset string
- `revset.default()` - Get default revset string
- `jj_async({...})` - Run JJ command asynchronously
- `jj({...})` - Run JJ command synchronously (returns output, err)
- `flash(message)` - Display flash message
- `copy_to_clipboard(text)` - Copy text to clipboard

Here are a couple of examples:

- Appends `| ancestors(<change id of the current revisions>, 2)` to the end of revset and bumps the number with each execution

```toml
[custom_commands.append_to_revset]
  key = ["+"]
  lua = '''
  local change_id = revisions.current()
  if not change_id then return end

  local current = revset.current()
  local bumped = false
  local updated = current:gsub("ancestors%(" .. change_id .. "%s*,%s*(%d+)%)", function(n)
    bumped = true
    return "ancestors(" .. change_id .. ", " .. (tonumber(n) + 1) .. ")"
  end, 1)

  if not bumped then
    updated = current .. "| ancestors(" .. change_id .. ", 2)"
  end

  revset.set(updated)
 '''
```

- Inserts a new commit after the selected one and then starts inline describe on the new revision.

```toml
[custom_commands.new_then_describe]
  key = ["N"]
  lua = '''
  jj("new", "-A", revisions.current())
  revisions.refresh()

  local new_change_id = jj("log", "-r", "@&#8203;", "-T", "change_id.shortest()", "--no-graph")
  revisions.navigate{to=new_change_id}
  revisions.start_inline_describe()
  '''
```

- Copy to clipboard example

```toml
[custom_commands.copy_to_clipboard]
  key = ["X"]
  lua = '''
  local selections = revisions.checked()
  if #selections == 0 then
    flash("none selected")
  end
  local content = table.concat(selections, ",")
  copy_to_clipboard(content)
  '''
```

**✨ Enhancements**

- **Key Sequences for Custom Commands ([#&#8203;420](idursun/jjui#420: Custom commands can now be triggered with multi-key sequences using `key_sequence` property. Also adds `desc` property for command descriptions. An overlay shows available sequences after pressing the first key.

  Example:

  ```toml
  [custom_commands.bookmark_list]
    key_sequence = ["w", "b", "l"]
    desc = "bookmarks list"
    lua = '''
    revset.set("bookmarks() | remote_bookmarks()")
    '''
  ```

- **Faster Refresh ([#&#8203;412](idursun/jjui#412: Improved streaming command handling, eliminating 100ms delay and making refreshes instant. Previously jjui would fail to launch or get stuck when jj emitted warning messages (e.g., deprecated config options like `git.push-new-bookmarks`).

- **Quick Search Highlighting ([#&#8203;414](idursun/jjui#414: Case-insensitive search with visual highlighting of all matches in the revisions view

- **Remember Unsaved Descriptions ([#&#8203;417](idursun/jjui#417: Descriptions are now preserved when you cancel, preventing accidental loss of work. Addresses the common frustration of accidentally hitting ESC and losing long commit messages with no way to recover them.

- **Squash Operation Toggle ([#&#8203;405](idursun/jjui#405: New `--use-destination-message` option for squash operations

**🐛 Bug Fixes**

- **Preview Panel Focus Issue ([#&#8203;390](idursun/jjui#390: Fixed preview panel showing full commit diff instead of selected file diff when terminal regains focus
- **EOF Error Handling ([#&#8203;418](idursun/jjui#418: Proper error messages when revset contains no revisions instead of getting stuck
- **Parser Color Agnostic ([#&#8203;413](idursun/jjui#413: Fixed parsing issues when users configure ChangeID/CommitID/IsDivergent with same colors.
- **Leader Key Timing ([#&#8203;416](idursun/jjui#416: Fixed leader key processing to prevent race conditions. Leader keys were completely non-functional in versions after v0.9.3 - the options would appear in the UI but do nothing when selected.

**🎨 UI/UX Improvements**

- Clear selected revisions with ESC key when not in editing/overlay/focused operations ([#&#8203;419](idursun/jjui#419))
- Better menu spacing for git and bookmarks
- Reduced preview debounce time back to 50ms for snappier response ([#&#8203;410](idursun/jjui#410)). The 200ms debounce made the UI feel sluggish when navigating between revisions.

**⚙️ Internal Improvements**

- Introduced intent-based architecture for better separation of concerns (only implemented for revisions, flash for now)
- Moved flash intents to dedicated package
- Simplified details view rendering
- Better configuration organisation

#### What's Changed

- operation: Add use destination message to squash operation by [@&#8203;woutersmeenk](https://github.com/woutersmeenk) in [#&#8203;405](idursun/jjui#405)
- Preview panel shows whole commit diff instead of selected file's diff when terminal regains focus by [@&#8203;abourget](https://github.com/abourget) in [#&#8203;390](idursun/jjui#390)
- fix(streamer): handle warning messages by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;412](idursun/jjui#412)
- parser: stringify log/evolog prefixes to be color agnostic by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;413](idursun/jjui#413)
- revisions: add highlight to QuickSearch, make search case insensitive by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;414](idursun/jjui#414)
- feat: Lua scripting in custom commands by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;415](idursun/jjui#415)
- revisions: handle EOF error for revset without revisions by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;418](idursun/jjui#418)
- revisions: clear selected revisions on cancel by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;419](idursun/jjui#419)
- feat: custom commands with sequence keys by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;420](idursun/jjui#420)

#### New Contributors

- [@&#8203;woutersmeenk](https://github.com/woutersmeenk) made their first contribution in [#&#8203;405](idursun/jjui#405)
- [@&#8203;abourget](https://github.com/abourget) made their first contribution in [#&#8203;390](idursun/jjui#390)

**Full Changelog**: <idursun/jjui@v0.9.7...v0.9.8>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever MR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi40Ny4wIiwidXBkYXRlZEluVmVyIjoiNDIuNDcuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90Il19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants