Skip to content

Conversation

@jmcphers
Copy link
Collaborator

A little holiday week project to add a history pane to Positron:

image

Some placeholder services had already been created this early in Positron's lifecycle, and we already save inputs for navigation/search in the console, so this mostly just connects wires and adds UX on top of existing services.

Features:

  • Similar design and goals compared to RStudio's history pane, adapted for Positron (e.g. multi-language, handle longer blocks, etc.)
  • Tracks project-scoped input history by language.
  • Groups history entries loosely by time period.
  • Efficient scrolling of long, syntax-highlighted histories with react-window.
  • Affordances to send current entry to the console or to the current editor, and to clear individual entries (e.g. to remove secrets) or the whole history.
  • Case-insensitive search for history entries.
  • Keyboard accessible.

Addresses #5484.

Release Notes

New Features

Bug Fixes

  • N/A

QA Notes

  • It turns out that we have not been correctly saving the time an input was entered, so all input prior to this PR will just show up as 'older'
  • History is currently per project, so if you open a project you will only see commands you have run in that project

@github-actions
Copy link

github-actions bot commented Nov 27, 2025

E2E Tests 🚀
This PR will run tests tagged with: @:critical

readme  valid tags

@dhruvisompura
Copy link
Contributor

dhruvisompura commented Dec 3, 2025

Looks like we may need to tweak the css so that the white background for the "no matches" view extends the height of the panel:

Screenshot 2025-12-02 at 4 24 41 PM

Edit: Looks like the history parent container isn't extending the full height. I noticed that with more content, there is still a gap at the bottom. I tried to capture it in this video:

Screen.Recording.2025-12-02.at.5.02.12.PM.mov

@dhruvisompura
Copy link
Contributor

Is deleting entries working for you? I found that deleting entries wasn't working for me. I tested against entries for R. The delete button doesn't do anything and I saw the following leaky disposable warning show up (they also appear during startup):

lifecycle.ts:51 [LEAKED DISPOSABLE] Error: CREATED via:
    at GCBasedDisposableTracker.trackDisposable (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/vs/base/common/lifecycle.js:28:23)
    at trackDisposable (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/vs/base/common/lifecycle.js:204:24)
    at new DisposableStore (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/vs/base/common/lifecycle.js:315:9)
    at PositronHistoryPanel (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/vs/workbench/contrib/positronHistory/browser/components/positronHistoryPanel.js:72:35)
    at $i (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/esm-package-dependencies/v135/[email protected]/es2022/react-dom.mjs:10:16950)
    at li (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/esm-package-dependencies/v135/[email protected]/es2022/react-dom.mjs:12:3130)
    at ca (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/esm-package-dependencies/v135/[email protected]/es2022/react-dom.mjs:12:44421)
    at oa (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/esm-package-dependencies/v135/[email protected]/es2022/react-dom.mjs:12:39453)
    at hf (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/esm-package-dependencies/v135/[email protected]/es2022/react-dom.mjs:12:39384)
    at jr (vscode-file://vscode-app/Users/dhruvisompura/development/positron/out/esm-package-dependencies/v135/[email protected]/es2022/react-dom.mjs:12:39243)

@dhruvisompura
Copy link
Contributor

Noticed a strange resizing issue where the scroll bar is shifting!

Screen.Recording.2025-12-02.at.4.39.12.PM.mov

@jmcphers
Copy link
Collaborator Author

jmcphers commented Dec 3, 2025

Is deleting entries working for you?

It is! I think the disposable leaks you're seeing are unrelated (just pushed a fix for those). Can you share more about the steps you're taking and what you're seeing?

@dhruvisompura
Copy link
Contributor

Is deleting entries working for you?

It is! I think the disposable leaks you're seeing are unrelated (just pushed a fix for those). Can you share more about the steps you're taking and what you're seeing?

Are you supposed to be able to delete stuff that falls under the "Older" grouping? I just tested things again and noticed that it works for stuff under "Today".

Here are the steps:

  1. Start Positron
  2. Navigate to history panel
  3. Select R from language dropdown
  4. Click on first item in history under "OLDER" grouping and then click the delete button. OBSERVE that item is not deleted
Screen.Recording.2025-12-02.at.5.09.39.PM.mov

@jmcphers
Copy link
Collaborator Author

jmcphers commented Dec 3, 2025

Noticed a strange resizing issue where the scroll bar is shifting!

This happens because we're using react-window, which requires that we supply an explicit width (and will only render in the rectangle supplied). We debounce the width we pass to react-window because asking it to redraw every time the size changes results in some pretty bad flickering artifacts that are worse than the behavior you see here. I think it's worth the tradeoff to get virtualized scrolling since these histories can get pretty long!

I toned down the debouncer in 275746a; LMK if you think it still feels too janky and I'll see if there are any other tricks we can use here.

@jmcphers
Copy link
Collaborator Author

jmcphers commented Dec 3, 2025

Are you supposed to be able to delete stuff that falls under the "Older" grouping?

You are! But I've done so many state clears that I didn't have anything in that grouping, which is why I couldn't reproduce this. Thanks, I'll take a look!

Copy link
Contributor

@dhruvisompura dhruvisompura left a comment

Choose a reason for hiding this comment

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

Not done with my review and will continue it on the following day, but here's my feedback so far!

}
}}
>
<div className="history-entry-content">
Copy link
Contributor

@dhruvisompura dhruvisompura Dec 3, 2025

Choose a reason for hiding this comment

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

super minor, but using double quotes for classnames normally throws an eslint error. These should be single quotes. There's a few places with this issue!

Suggested change
<div className="history-entry-content">
<div className='history-entry-content'>

{/* Show "... N more lines" above if smart excerpt hides lines above */}
{smartExcerpt && smartExcerpt.hiddenAbove > 0 && (
<div className="history-entry-line-indicator">
... {smartExcerpt.hiddenAbove} more lines
Copy link
Contributor

Choose a reason for hiding this comment

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

This string should probably be localized!

Comment on lines +67 to +75
// Input key down handler.
const inputKeyDownHandler = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Escape' && filterText !== '') {
e.preventDefault();
e.stopPropagation();
buttonClearClickHandler();
}
};

Copy link
Contributor

Choose a reason for hiding this comment

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

This was a nice addition and we get it for free in the data explorer and variables pane!

Comment on lines 573 to 580
onMouseDown={(e) => {
// Use onMouseDown instead of onClick to ensure selection happens before focus events
// This prevents the two-click issue when the panel is unfocused
if (e.button === 0) { // Only handle left clicks
e.preventDefault(); // Prevent default focus behavior that steals focus
onSelect();
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have a video of this "two-click" issue? I'm trying to understand what may have been happening. Generally, an onClick handler would be what we'd want to use. Having to use onMouseDown makes me think there is a greater focus/selection bug.

const styleWithoutHeight = { ...style, height: 'auto' };

return (
<div
Copy link
Contributor

@dhruvisompura dhruvisompura Dec 3, 2025

Choose a reason for hiding this comment

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

I know we have divs with click elements in quite a number of places but an unstyled button element would be a much better approach here. We get accessibility and keyboard navigation for free which means a lot less code and semantically divs aren't supposed to be used for interactive elements.

This does make me think a reusable unstyled button element would be really useful for most of our panes!

Comment on lines 26 to 32
return (
<div className="history-separator" style={customStyle}>
<div className="history-separator-content">
<span className="history-separator-label">{label}</span>
</div>
</div>
);
Copy link
Contributor

@dhruvisompura dhruvisompura Dec 3, 2025

Choose a reason for hiding this comment

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

Just a thought on a future improvement: while playing with this, I really wanted to be able to click this separator so I could collapse the groupings since I had a lot of OLDER entries and didn't want to scroll all the way to the bottom to see the history entries from TODAY.

Comment on lines 996 to 1002
{stickyHeaderLabel && listItems.length > 0 && (
<div className='history-sticky-header'>
<div className='history-separator-content'>
<span className='history-separator-label'>{stickyHeaderLabel}</span>
</div>
</div>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could just use the historySeparator component and pass in the history-sticky-header class as a prop.

);
}
// Create and append the Positron history container
this._positronHistoryContainer = DOM.$('.positron-history-container');
Copy link
Contributor

Choose a reason for hiding this comment

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

This container doesn't look to be extending the full height of the parent pane. We need a height: 100% on this element like we have for our other containers.

@dhruvisompura
Copy link
Contributor

Just tested the delete functionality again, and noticed that deleting a single older entry still not working as expected. I tried to delete the only entry I had under "OLDER". That didn't seem to work and I noticed an entry below ended up getting deleted. 😭

Out of curiosity, is there an easy way to mock out a ton of history for the panel that is older for testing? I was hoping to dig into why that was happening but I can't reliably reproduce without older entries (anything not from "Today")

Screen.Recording.2025-12-04.at.9.49.14.AM.mov

@dhruvisompura
Copy link
Contributor

Noticed a strange resizing issue where the scroll bar is shifting!

This happens because we're using react-window, which requires that we supply an explicit width (and will only render in the rectangle supplied). We debounce the width we pass to react-window because asking it to redraw every time the size changes results in some pretty bad flickering artifacts that are worse than the behavior you see here. I think it's worth the tradeoff to get virtualized scrolling since these histories can get pretty long!

I toned down the debouncer in 275746a; LMK if you think it still feels too janky and I'll see if there are any other tricks we can use here.

I just pulled down the changes and gave them a test run. I didn't notice a visible improvement with the debounce. It's still very noticeable that the vertical scrollbar is trying to run away 😂.

Screen.Recording.2025-12-04.at.9.46.56.AM.mov

@dhruvisompura
Copy link
Contributor

I definitely think there is something wonky going on with the history container and getting it to take up the full height of the panel in the secondary side bar...the scrollbar isn't extending the full height and its causing some issues when you try and scroll to the bottom to see the last item.

Screen.Recording.2025-12-04.at.10.15.07.AM.mov
Screen.Recording.2025-12-04.at.9.46.56.AM.copy.mov

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