diffstalker 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +8 -0
- package/bun.lock +23 -0
- package/dist/App.js +225 -471
- package/dist/FollowMode.js +85 -0
- package/dist/KeyBindings.js +178 -0
- package/dist/MouseHandlers.js +156 -0
- package/dist/core/ExplorerStateManager.js +444 -78
- package/dist/core/GitStateManager.js +169 -93
- package/dist/git/diff.js +4 -0
- package/dist/index.js +54 -53
- package/dist/state/UIState.js +17 -4
- package/dist/ui/PaneRenderers.js +56 -0
- package/dist/ui/modals/FileFinder.js +232 -0
- package/dist/ui/widgets/CompareListView.js +86 -64
- package/dist/ui/widgets/DiffView.js +19 -17
- package/dist/ui/widgets/ExplorerContent.js +15 -28
- package/dist/ui/widgets/ExplorerView.js +140 -31
- package/dist/ui/widgets/Footer.js +6 -2
- package/dist/ui/widgets/Header.js +3 -46
- package/dist/utils/fileCategories.js +37 -0
- package/dist/utils/fileTree.js +148 -0
- package/eslint.metrics.js +16 -0
- package/metrics/.gitkeep +0 -0
- package/metrics/v0.2.1.json +268 -0
- package/package.json +4 -1
- package/dist/utils/ansiToBlessed.js +0 -125
- package/dist/utils/mouseCoordinates.js +0 -165
- package/dist/utils/rowCalculations.js +0 -246
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { FilePathWatcher } from './core/FilePathWatcher.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manages the file-watching follow mode.
|
|
4
|
+
* Watches a target file for repository path changes and file navigation.
|
|
5
|
+
*/
|
|
6
|
+
export class FollowMode {
|
|
7
|
+
targetFile;
|
|
8
|
+
getCurrentRepoPath;
|
|
9
|
+
callbacks;
|
|
10
|
+
watcher = null;
|
|
11
|
+
_watcherState = { enabled: false };
|
|
12
|
+
constructor(targetFile, getCurrentRepoPath, callbacks) {
|
|
13
|
+
this.targetFile = targetFile;
|
|
14
|
+
this.getCurrentRepoPath = getCurrentRepoPath;
|
|
15
|
+
this.callbacks = callbacks;
|
|
16
|
+
}
|
|
17
|
+
get watcherState() {
|
|
18
|
+
return this._watcherState;
|
|
19
|
+
}
|
|
20
|
+
get isEnabled() {
|
|
21
|
+
return this.watcher !== null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Start watching the target file.
|
|
25
|
+
*/
|
|
26
|
+
start() {
|
|
27
|
+
this.watcher = new FilePathWatcher(this.targetFile);
|
|
28
|
+
this.watcher.on('path-change', (state) => {
|
|
29
|
+
if (state.path && state.path !== this.getCurrentRepoPath()) {
|
|
30
|
+
this._watcherState = {
|
|
31
|
+
enabled: true,
|
|
32
|
+
sourceFile: state.sourceFile ?? this.targetFile,
|
|
33
|
+
rawContent: state.rawContent ?? undefined,
|
|
34
|
+
lastUpdate: state.lastUpdate ?? undefined,
|
|
35
|
+
};
|
|
36
|
+
this.callbacks.onRepoChange(state.path, this._watcherState);
|
|
37
|
+
}
|
|
38
|
+
// Navigate to the followed file if it's within the repo
|
|
39
|
+
if (state.rawContent) {
|
|
40
|
+
this.callbacks.onFileNavigate(state.rawContent);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
this._watcherState = {
|
|
44
|
+
enabled: true,
|
|
45
|
+
sourceFile: this.targetFile,
|
|
46
|
+
};
|
|
47
|
+
this.watcher.start();
|
|
48
|
+
// Switch to the repo described in the target file
|
|
49
|
+
const initialState = this.watcher.state;
|
|
50
|
+
if (initialState.path && initialState.path !== this.getCurrentRepoPath()) {
|
|
51
|
+
this._watcherState = {
|
|
52
|
+
enabled: true,
|
|
53
|
+
sourceFile: initialState.sourceFile ?? this.targetFile,
|
|
54
|
+
rawContent: initialState.rawContent ?? undefined,
|
|
55
|
+
lastUpdate: initialState.lastUpdate ?? undefined,
|
|
56
|
+
};
|
|
57
|
+
this.callbacks.onRepoChange(initialState.path, this._watcherState);
|
|
58
|
+
}
|
|
59
|
+
else if (initialState.rawContent) {
|
|
60
|
+
this._watcherState.rawContent = initialState.rawContent;
|
|
61
|
+
this.callbacks.onFileNavigate(initialState.rawContent);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Toggle follow mode on/off.
|
|
66
|
+
*/
|
|
67
|
+
toggle() {
|
|
68
|
+
if (this.watcher) {
|
|
69
|
+
this.stop();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.start();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Stop watching.
|
|
77
|
+
*/
|
|
78
|
+
stop() {
|
|
79
|
+
if (this.watcher) {
|
|
80
|
+
this.watcher.stop();
|
|
81
|
+
this.watcher = null;
|
|
82
|
+
this._watcherState = { enabled: false };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { SPLIT_RATIO_STEP } from './ui/Layout.js';
|
|
2
|
+
import { getFileAtIndex } from './ui/widgets/FileList.js';
|
|
3
|
+
/**
|
|
4
|
+
* Register all keyboard bindings on the blessed screen.
|
|
5
|
+
*/
|
|
6
|
+
export function setupKeyBindings(screen, actions, ctx) {
|
|
7
|
+
// Quit
|
|
8
|
+
screen.key(['q', 'C-c'], () => {
|
|
9
|
+
actions.exit();
|
|
10
|
+
});
|
|
11
|
+
// Navigation (skip if modal is open)
|
|
12
|
+
screen.key(['j', 'down'], () => {
|
|
13
|
+
if (ctx.hasActiveModal())
|
|
14
|
+
return;
|
|
15
|
+
actions.navigateDown();
|
|
16
|
+
});
|
|
17
|
+
screen.key(['k', 'up'], () => {
|
|
18
|
+
if (ctx.hasActiveModal())
|
|
19
|
+
return;
|
|
20
|
+
actions.navigateUp();
|
|
21
|
+
});
|
|
22
|
+
// Tab switching (skip if modal is open)
|
|
23
|
+
const tabs = [
|
|
24
|
+
['1', 'diff'],
|
|
25
|
+
['2', 'commit'],
|
|
26
|
+
['3', 'history'],
|
|
27
|
+
['4', 'compare'],
|
|
28
|
+
['5', 'explorer'],
|
|
29
|
+
];
|
|
30
|
+
for (const [key, tab] of tabs) {
|
|
31
|
+
screen.key([key], () => {
|
|
32
|
+
if (ctx.hasActiveModal())
|
|
33
|
+
return;
|
|
34
|
+
ctx.uiState.setTab(tab);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Pane toggle (skip if modal is open)
|
|
38
|
+
screen.key(['tab'], () => {
|
|
39
|
+
if (ctx.hasActiveModal())
|
|
40
|
+
return;
|
|
41
|
+
ctx.uiState.togglePane();
|
|
42
|
+
});
|
|
43
|
+
// Staging operations (skip if modal is open)
|
|
44
|
+
screen.key(['s'], () => {
|
|
45
|
+
if (ctx.hasActiveModal())
|
|
46
|
+
return;
|
|
47
|
+
actions.stageSelected();
|
|
48
|
+
});
|
|
49
|
+
screen.key(['S-u'], () => {
|
|
50
|
+
if (ctx.hasActiveModal())
|
|
51
|
+
return;
|
|
52
|
+
actions.unstageSelected();
|
|
53
|
+
});
|
|
54
|
+
screen.key(['S-a'], () => {
|
|
55
|
+
if (ctx.hasActiveModal())
|
|
56
|
+
return;
|
|
57
|
+
actions.stageAll();
|
|
58
|
+
});
|
|
59
|
+
screen.key(['S-z'], () => {
|
|
60
|
+
if (ctx.hasActiveModal())
|
|
61
|
+
return;
|
|
62
|
+
actions.unstageAll();
|
|
63
|
+
});
|
|
64
|
+
// Select/toggle (skip if modal is open)
|
|
65
|
+
screen.key(['enter', 'space'], () => {
|
|
66
|
+
if (ctx.hasActiveModal())
|
|
67
|
+
return;
|
|
68
|
+
if (ctx.getBottomTab() === 'explorer' && ctx.getCurrentPane() === 'explorer') {
|
|
69
|
+
actions.enterExplorerDirectory();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
actions.toggleSelected();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// Explorer: go up directory (skip if modal is open)
|
|
76
|
+
screen.key(['backspace'], () => {
|
|
77
|
+
if (ctx.hasActiveModal())
|
|
78
|
+
return;
|
|
79
|
+
if (ctx.getBottomTab() === 'explorer' && ctx.getCurrentPane() === 'explorer') {
|
|
80
|
+
actions.goExplorerUp();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// Explorer: toggle show only changes filter
|
|
84
|
+
screen.key(['g'], () => {
|
|
85
|
+
if (ctx.hasActiveModal())
|
|
86
|
+
return;
|
|
87
|
+
if (ctx.getBottomTab() === 'explorer') {
|
|
88
|
+
ctx.explorerManager?.toggleShowOnlyChanges();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
// Explorer: open file finder
|
|
92
|
+
screen.key(['/'], () => {
|
|
93
|
+
if (ctx.hasActiveModal())
|
|
94
|
+
return;
|
|
95
|
+
if (ctx.getBottomTab() === 'explorer') {
|
|
96
|
+
actions.openFileFinder();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
// Commit (skip if modal is open)
|
|
100
|
+
screen.key(['c'], () => {
|
|
101
|
+
if (ctx.hasActiveModal())
|
|
102
|
+
return;
|
|
103
|
+
ctx.uiState.setTab('commit');
|
|
104
|
+
});
|
|
105
|
+
// Commit panel specific keys (only when on commit tab)
|
|
106
|
+
screen.key(['i'], () => {
|
|
107
|
+
if (ctx.getBottomTab() === 'commit' && !ctx.isCommitInputFocused()) {
|
|
108
|
+
actions.focusCommitInput();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
screen.key(['a'], () => {
|
|
112
|
+
if (ctx.getBottomTab() === 'commit' && !ctx.isCommitInputFocused()) {
|
|
113
|
+
ctx.commitFlowState.toggleAmend();
|
|
114
|
+
actions.render();
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
ctx.uiState.toggleAutoTab();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
screen.key(['escape'], () => {
|
|
121
|
+
if (ctx.getBottomTab() === 'commit') {
|
|
122
|
+
if (ctx.isCommitInputFocused()) {
|
|
123
|
+
actions.unfocusCommitInput();
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
ctx.uiState.setTab('diff');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
// Refresh
|
|
131
|
+
screen.key(['r'], () => actions.refresh());
|
|
132
|
+
// Display toggles
|
|
133
|
+
screen.key(['w'], () => ctx.uiState.toggleWrapMode());
|
|
134
|
+
screen.key(['m'], () => actions.toggleMouseMode());
|
|
135
|
+
screen.key(['S-t'], () => ctx.uiState.toggleAutoTab());
|
|
136
|
+
// Split ratio adjustments
|
|
137
|
+
screen.key(['-', '_', '['], () => {
|
|
138
|
+
ctx.uiState.adjustSplitRatio(-SPLIT_RATIO_STEP);
|
|
139
|
+
ctx.layout.setSplitRatio(ctx.uiState.state.splitRatio);
|
|
140
|
+
actions.render();
|
|
141
|
+
});
|
|
142
|
+
screen.key(['=', '+', ']'], () => {
|
|
143
|
+
ctx.uiState.adjustSplitRatio(SPLIT_RATIO_STEP);
|
|
144
|
+
ctx.layout.setSplitRatio(ctx.uiState.state.splitRatio);
|
|
145
|
+
actions.render();
|
|
146
|
+
});
|
|
147
|
+
// Theme picker
|
|
148
|
+
screen.key(['t'], () => ctx.uiState.openModal('theme'));
|
|
149
|
+
// Hotkeys modal
|
|
150
|
+
screen.key(['?'], () => ctx.uiState.toggleModal('hotkeys'));
|
|
151
|
+
// Follow toggle
|
|
152
|
+
screen.key(['f'], () => actions.toggleFollow());
|
|
153
|
+
// Compare view: base branch picker
|
|
154
|
+
screen.key(['b'], () => {
|
|
155
|
+
if (ctx.getBottomTab() === 'compare') {
|
|
156
|
+
ctx.uiState.openModal('baseBranch');
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
// Compare view: toggle uncommitted
|
|
160
|
+
screen.key(['u'], () => {
|
|
161
|
+
if (ctx.getBottomTab() === 'compare') {
|
|
162
|
+
ctx.uiState.toggleIncludeUncommitted();
|
|
163
|
+
const includeUncommitted = ctx.uiState.state.includeUncommitted;
|
|
164
|
+
ctx.gitManager?.refreshCompareDiff(includeUncommitted);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// Discard changes (with confirmation)
|
|
168
|
+
screen.key(['d'], () => {
|
|
169
|
+
if (ctx.getBottomTab() === 'diff') {
|
|
170
|
+
const files = ctx.getStatusFiles();
|
|
171
|
+
const selectedFile = getFileAtIndex(files, ctx.getSelectedIndex());
|
|
172
|
+
// Only allow discard for unstaged modified files
|
|
173
|
+
if (selectedFile && !selectedFile.staged && selectedFile.status !== 'untracked') {
|
|
174
|
+
actions.showDiscardConfirm(selectedFile);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { getFileListTotalRows, getFileIndexFromRow } from './ui/widgets/FileList.js';
|
|
2
|
+
import { getCompareListTotalRows, getCompareSelectionFromRow, } from './ui/widgets/CompareListView.js';
|
|
3
|
+
import { getExplorerTotalRows } from './ui/widgets/ExplorerView.js';
|
|
4
|
+
import { getExplorerContentTotalRows } from './ui/widgets/ExplorerContent.js';
|
|
5
|
+
const SCROLL_AMOUNT = 3;
|
|
6
|
+
/**
|
|
7
|
+
* Register all mouse event handlers on the layout.
|
|
8
|
+
*/
|
|
9
|
+
export function setupMouseHandlers(layout, actions, ctx) {
|
|
10
|
+
// Mouse wheel on top pane
|
|
11
|
+
layout.topPane.on('wheeldown', () => {
|
|
12
|
+
handleTopPaneScroll(SCROLL_AMOUNT, layout, ctx);
|
|
13
|
+
});
|
|
14
|
+
layout.topPane.on('wheelup', () => {
|
|
15
|
+
handleTopPaneScroll(-SCROLL_AMOUNT, layout, ctx);
|
|
16
|
+
});
|
|
17
|
+
// Mouse wheel on bottom pane
|
|
18
|
+
layout.bottomPane.on('wheeldown', () => {
|
|
19
|
+
handleBottomPaneScroll(SCROLL_AMOUNT, layout, ctx);
|
|
20
|
+
});
|
|
21
|
+
layout.bottomPane.on('wheelup', () => {
|
|
22
|
+
handleBottomPaneScroll(-SCROLL_AMOUNT, layout, ctx);
|
|
23
|
+
});
|
|
24
|
+
// Click on top pane to select item
|
|
25
|
+
layout.topPane.on('click', (mouse) => {
|
|
26
|
+
const clickedRow = layout.screenYToTopPaneRow(mouse.y);
|
|
27
|
+
if (clickedRow >= 0) {
|
|
28
|
+
handleTopPaneClick(clickedRow, mouse.x, actions, ctx);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Click on footer for tabs and toggles
|
|
32
|
+
layout.footerBox.on('click', (mouse) => {
|
|
33
|
+
handleFooterClick(mouse.x, actions, ctx);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function handleTopPaneClick(row, x, actions, ctx) {
|
|
37
|
+
const state = ctx.uiState.state;
|
|
38
|
+
if (state.bottomTab === 'history') {
|
|
39
|
+
const index = state.historyScrollOffset + row;
|
|
40
|
+
ctx.uiState.setHistorySelectedIndex(index);
|
|
41
|
+
actions.selectHistoryCommitByIndex(index);
|
|
42
|
+
}
|
|
43
|
+
else if (state.bottomTab === 'compare') {
|
|
44
|
+
const commits = ctx.getCompareCommits();
|
|
45
|
+
const files = ctx.getCompareFiles();
|
|
46
|
+
const selection = getCompareSelectionFromRow(state.compareScrollOffset + row, commits, files);
|
|
47
|
+
if (selection) {
|
|
48
|
+
actions.selectCompareItem(selection);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (state.bottomTab === 'explorer') {
|
|
52
|
+
const index = state.explorerScrollOffset + row;
|
|
53
|
+
ctx.explorerManager?.selectIndex(index);
|
|
54
|
+
ctx.uiState.setExplorerSelectedIndex(index);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Diff tab - select file
|
|
58
|
+
const files = ctx.getStatusFiles();
|
|
59
|
+
const fileIndex = getFileIndexFromRow(row + state.fileListScrollOffset, files);
|
|
60
|
+
if (fileIndex !== null && fileIndex >= 0) {
|
|
61
|
+
// Check if click is on the action button [+] or [-] (columns 2-4)
|
|
62
|
+
if (x !== undefined && x >= 2 && x <= 4) {
|
|
63
|
+
actions.toggleFileByIndex(fileIndex);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
ctx.uiState.setSelectedIndex(fileIndex);
|
|
67
|
+
actions.selectFileByIndex(fileIndex);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function handleFooterClick(x, actions, ctx) {
|
|
73
|
+
const width = ctx.getScreenWidth();
|
|
74
|
+
// Tabs are right-aligned
|
|
75
|
+
const tabPositions = [
|
|
76
|
+
{ tab: 'explorer', width: 11 },
|
|
77
|
+
{ tab: 'compare', width: 10 },
|
|
78
|
+
{ tab: 'history', width: 10 },
|
|
79
|
+
{ tab: 'commit', width: 9 },
|
|
80
|
+
{ tab: 'diff', width: 7 },
|
|
81
|
+
];
|
|
82
|
+
let rightEdge = width;
|
|
83
|
+
for (const { tab, width: tabWidth } of tabPositions) {
|
|
84
|
+
const leftEdge = rightEdge - tabWidth - 1;
|
|
85
|
+
if (x >= leftEdge && x < rightEdge) {
|
|
86
|
+
ctx.uiState.setTab(tab);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
rightEdge = leftEdge;
|
|
90
|
+
}
|
|
91
|
+
// Left side toggles (approximate positions)
|
|
92
|
+
if (x >= 2 && x <= 9) {
|
|
93
|
+
actions.toggleMouseMode();
|
|
94
|
+
}
|
|
95
|
+
else if (x >= 11 && x <= 16) {
|
|
96
|
+
ctx.uiState.toggleAutoTab();
|
|
97
|
+
}
|
|
98
|
+
else if (x >= 18 && x <= 23) {
|
|
99
|
+
ctx.uiState.toggleWrapMode();
|
|
100
|
+
}
|
|
101
|
+
else if (x >= 25 && x <= 32) {
|
|
102
|
+
actions.toggleFollow();
|
|
103
|
+
}
|
|
104
|
+
else if (x >= 34 && x <= 43 && ctx.uiState.state.bottomTab === 'explorer') {
|
|
105
|
+
ctx.explorerManager?.toggleShowOnlyChanges();
|
|
106
|
+
}
|
|
107
|
+
else if (x === 0) {
|
|
108
|
+
ctx.uiState.openModal('hotkeys');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function handleTopPaneScroll(delta, layout, ctx) {
|
|
112
|
+
const state = ctx.uiState.state;
|
|
113
|
+
const visibleHeight = layout.dimensions.topPaneHeight;
|
|
114
|
+
if (state.bottomTab === 'history') {
|
|
115
|
+
const totalRows = ctx.getHistoryCommitCount();
|
|
116
|
+
const maxOffset = Math.max(0, totalRows - visibleHeight);
|
|
117
|
+
const newOffset = Math.min(maxOffset, Math.max(0, state.historyScrollOffset + delta));
|
|
118
|
+
ctx.uiState.setHistoryScrollOffset(newOffset);
|
|
119
|
+
}
|
|
120
|
+
else if (state.bottomTab === 'compare') {
|
|
121
|
+
const totalRows = getCompareListTotalRows(ctx.getCompareCommits(), ctx.getCompareFiles());
|
|
122
|
+
const maxOffset = Math.max(0, totalRows - visibleHeight);
|
|
123
|
+
const newOffset = Math.min(maxOffset, Math.max(0, state.compareScrollOffset + delta));
|
|
124
|
+
ctx.uiState.setCompareScrollOffset(newOffset);
|
|
125
|
+
}
|
|
126
|
+
else if (state.bottomTab === 'explorer') {
|
|
127
|
+
const totalRows = getExplorerTotalRows(ctx.explorerManager?.state.displayRows ?? []);
|
|
128
|
+
const maxOffset = Math.max(0, totalRows - visibleHeight);
|
|
129
|
+
const newOffset = Math.min(maxOffset, Math.max(0, state.explorerScrollOffset + delta));
|
|
130
|
+
ctx.uiState.setExplorerScrollOffset(newOffset);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const files = ctx.getStatusFiles();
|
|
134
|
+
const totalRows = getFileListTotalRows(files);
|
|
135
|
+
const maxOffset = Math.max(0, totalRows - visibleHeight);
|
|
136
|
+
const newOffset = Math.min(maxOffset, Math.max(0, state.fileListScrollOffset + delta));
|
|
137
|
+
ctx.uiState.setFileListScrollOffset(newOffset);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function handleBottomPaneScroll(delta, layout, ctx) {
|
|
141
|
+
const state = ctx.uiState.state;
|
|
142
|
+
const visibleHeight = layout.dimensions.bottomPaneHeight;
|
|
143
|
+
const width = ctx.getScreenWidth();
|
|
144
|
+
if (state.bottomTab === 'explorer') {
|
|
145
|
+
const selectedFile = ctx.explorerManager?.state.selectedFile;
|
|
146
|
+
const totalRows = getExplorerContentTotalRows(selectedFile?.content ?? null, selectedFile?.path ?? null, selectedFile?.truncated ?? false, width, state.wrapMode);
|
|
147
|
+
const maxOffset = Math.max(0, totalRows - visibleHeight);
|
|
148
|
+
const newOffset = Math.min(maxOffset, Math.max(0, state.explorerFileScrollOffset + delta));
|
|
149
|
+
ctx.uiState.setExplorerFileScrollOffset(newOffset);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const maxOffset = Math.max(0, ctx.getBottomPaneTotalRows() - visibleHeight);
|
|
153
|
+
const newOffset = Math.min(maxOffset, Math.max(0, state.diffScrollOffset + delta));
|
|
154
|
+
ctx.uiState.setDiffScrollOffset(newOffset);
|
|
155
|
+
}
|
|
156
|
+
}
|