startall 0.0.10 → 0.0.12
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/index.js +122 -39
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -328,6 +328,7 @@ class ProcessManager {
|
|
|
328
328
|
this.processes = new Map();
|
|
329
329
|
this.processRefs = new Map();
|
|
330
330
|
this.outputLines = [];
|
|
331
|
+
this.totalLinesReceived = 0; // Track total lines ever received (never resets)
|
|
331
332
|
this.filter = '';
|
|
332
333
|
this.maxOutputLines = 1000;
|
|
333
334
|
this.maxVisibleLines = null; // Calculated dynamically based on screen height
|
|
@@ -357,6 +358,10 @@ class ProcessManager {
|
|
|
357
358
|
this.splitMode = false; // Whether waiting for split command after Ctrl+b
|
|
358
359
|
this.showSplitMenu = false; // Whether to show the command palette
|
|
359
360
|
this.splitMenuIndex = 0; // Selected item in split menu
|
|
361
|
+
this.paneScrollPositions = new Map(); // Store scroll positions per pane ID
|
|
362
|
+
this.paneScrollBoxes = new Map(); // Store ScrollBox references per pane ID
|
|
363
|
+
this.paneFilterState = new Map(); // Track filter state per pane to detect changes
|
|
364
|
+
this.paneLineCount = new Map(); // Track how many lines we've rendered per pane
|
|
360
365
|
|
|
361
366
|
// Assign colors to each script
|
|
362
367
|
this.processColors = new Map();
|
|
@@ -730,6 +735,7 @@ class ProcessManager {
|
|
|
730
735
|
process: processName,
|
|
731
736
|
text,
|
|
732
737
|
timestamp: Date.now(),
|
|
738
|
+
lineNumber: ++this.totalLinesReceived, // Track absolute line number
|
|
733
739
|
});
|
|
734
740
|
|
|
735
741
|
if (this.outputLines.length > this.maxOutputLines) {
|
|
@@ -744,13 +750,10 @@ class ProcessManager {
|
|
|
744
750
|
}
|
|
745
751
|
|
|
746
752
|
scheduleRender() {
|
|
747
|
-
//
|
|
748
|
-
if (this.
|
|
749
|
-
this.renderScheduled = true;
|
|
750
|
-
setTimeout(() => {
|
|
751
|
-
this.renderScheduled = false;
|
|
753
|
+
// Update the DOM - OpenTUI's render loop will pick up changes automatically
|
|
754
|
+
if (!this.destroyed) {
|
|
752
755
|
this.render();
|
|
753
|
-
}
|
|
756
|
+
}
|
|
754
757
|
}
|
|
755
758
|
|
|
756
759
|
stopProcess(scriptName) {
|
|
@@ -1811,9 +1814,58 @@ class ProcessManager {
|
|
|
1811
1814
|
}
|
|
1812
1815
|
|
|
1813
1816
|
updateRunningUI() {
|
|
1814
|
-
//
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
+
// Update existing panes incrementally, or rebuild if needed
|
|
1818
|
+
if (this.paneScrollBoxes.size > 0) {
|
|
1819
|
+
// Incremental update - just append new lines to existing panes
|
|
1820
|
+
let hasNewContent = false;
|
|
1821
|
+
|
|
1822
|
+
for (const [paneId, scrollBox] of this.paneScrollBoxes.entries()) {
|
|
1823
|
+
const pane = findPaneById(this.paneRoot, paneId);
|
|
1824
|
+
if (pane && scrollBox && scrollBox.content) {
|
|
1825
|
+
const lines = this.getOutputLinesForPane(pane);
|
|
1826
|
+
const lastRenderedLineNumber = this.paneLineCount.get(paneId) || 0;
|
|
1827
|
+
|
|
1828
|
+
// Find lines that haven't been rendered yet (based on absolute line number)
|
|
1829
|
+
const newLines = lines.filter(line => line.lineNumber > lastRenderedLineNumber);
|
|
1830
|
+
|
|
1831
|
+
if (newLines.length > 0) {
|
|
1832
|
+
hasNewContent = true;
|
|
1833
|
+
|
|
1834
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
1835
|
+
const line = newLines[i];
|
|
1836
|
+
const processColor = this.processColors.get(line.process) || COLORS.text;
|
|
1837
|
+
const trimmedText = line.text.trim();
|
|
1838
|
+
const lineNumber = String(line.lineNumber).padStart(4, ' ');
|
|
1839
|
+
|
|
1840
|
+
const outputLine = new TextRenderable(this.renderer, {
|
|
1841
|
+
id: `output-${pane.id}-${line.lineNumber}`,
|
|
1842
|
+
content: t`${fg(COLORS.textDim)(lineNumber)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`,
|
|
1843
|
+
bg: '#000000',
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
scrollBox.content.add(outputLine);
|
|
1847
|
+
|
|
1848
|
+
// Remove oldest line if we exceed maxOutputLines to maintain rolling window
|
|
1849
|
+
if (scrollBox.content.children && scrollBox.content.children.length > this.maxOutputLines) {
|
|
1850
|
+
const oldestChild = scrollBox.content.children[0];
|
|
1851
|
+
scrollBox.content.remove(oldestChild);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// Update to track the last absolute line number we rendered
|
|
1856
|
+
this.paneLineCount.set(paneId, newLines[newLines.length - 1].lineNumber);
|
|
1857
|
+
|
|
1858
|
+
// Auto-scroll to bottom if not paused
|
|
1859
|
+
if (!this.isPaused && scrollBox.scrollTo) {
|
|
1860
|
+
scrollBox.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
} else {
|
|
1866
|
+
// First time or no panes exist - do full rebuild
|
|
1867
|
+
this.buildRunningUI();
|
|
1868
|
+
}
|
|
1817
1869
|
}
|
|
1818
1870
|
|
|
1819
1871
|
// Build a single pane's output area
|
|
@@ -1821,36 +1873,25 @@ class ProcessManager {
|
|
|
1821
1873
|
const isFocused = pane.id === this.focusedPaneId;
|
|
1822
1874
|
const lines = this.getOutputLinesForPane(pane);
|
|
1823
1875
|
|
|
1824
|
-
//
|
|
1825
|
-
const
|
|
1826
|
-
let linesToShow = this.isPaused ? lines : lines.slice(-outputHeight);
|
|
1876
|
+
// Don't slice - show all lines and let ScrollBox handle scrolling
|
|
1877
|
+
const linesToShow = lines;
|
|
1827
1878
|
|
|
1828
|
-
// Add lines
|
|
1829
|
-
for (let i =
|
|
1879
|
+
// Add lines (oldest first, so newest is at bottom)
|
|
1880
|
+
for (let i = 0; i < linesToShow.length; i++) {
|
|
1830
1881
|
const line = linesToShow[i];
|
|
1831
1882
|
const processColor = this.processColors.get(line.process) || COLORS.text;
|
|
1832
1883
|
|
|
1833
|
-
//
|
|
1884
|
+
// Trim whitespace and let text wrap naturally - ScrollBox will handle overflow
|
|
1885
|
+
const trimmedText = line.text.trim();
|
|
1886
|
+
const lineNumber = String(i + 1).padStart(4, ' ');
|
|
1834
1887
|
const outputLine = new TextRenderable(this.renderer, {
|
|
1835
1888
|
id: `output-${pane.id}-${i}`,
|
|
1836
|
-
content: t`${fg(processColor)(`[${line.process}]`)} ${
|
|
1889
|
+
content: t`${fg(COLORS.textDim)(lineNumber)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`,
|
|
1837
1890
|
bg: '#000000', // Black background for pane content
|
|
1838
1891
|
});
|
|
1839
1892
|
|
|
1840
1893
|
container.add(outputLine);
|
|
1841
1894
|
}
|
|
1842
|
-
|
|
1843
|
-
// Fill remaining vertical space with blank lines
|
|
1844
|
-
const emptyLinesNeeded = Math.max(0, outputHeight - linesToShow.length);
|
|
1845
|
-
for (let j = 0; j < emptyLinesNeeded; j++) {
|
|
1846
|
-
const emptyLine = new TextRenderable(this.renderer, {
|
|
1847
|
-
id: `empty-${pane.id}-${j}`,
|
|
1848
|
-
content: ' ',
|
|
1849
|
-
bg: '#000000', // Black background for empty lines
|
|
1850
|
-
});
|
|
1851
|
-
|
|
1852
|
-
container.add(emptyLine);
|
|
1853
|
-
}
|
|
1854
1895
|
}
|
|
1855
1896
|
|
|
1856
1897
|
// Count how many vertical panes exist (for width calculation)
|
|
@@ -1900,22 +1941,52 @@ class ProcessManager {
|
|
|
1900
1941
|
backgroundColor: '#000000', // Black background for pane container
|
|
1901
1942
|
});
|
|
1902
1943
|
|
|
1903
|
-
// Output content - use
|
|
1944
|
+
// Output content - use ScrollBox to handle text wrapping properly
|
|
1904
1945
|
// Use passed height or calculate default for line count calculation
|
|
1905
1946
|
const height = availableHeight ? Math.max(5, availableHeight - 2) : Math.max(5, this.renderer.height - 6);
|
|
1906
1947
|
|
|
1907
|
-
const outputBox = new
|
|
1948
|
+
const outputBox = new ScrollBoxRenderable(this.renderer, {
|
|
1908
1949
|
id: `pane-output-${pane.id}`,
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1950
|
+
height: height,
|
|
1951
|
+
scrollX: false, // Disable horizontal scrollbar entirely
|
|
1952
|
+
scrollY: true, // Enable vertical scrolling
|
|
1953
|
+
focusable: true, // Enable mouse interactions and keyboard scrolling
|
|
1954
|
+
style: {
|
|
1955
|
+
rootOptions: {
|
|
1956
|
+
flexGrow: 1,
|
|
1957
|
+
flexShrink: 1,
|
|
1958
|
+
flexBasis: 0,
|
|
1959
|
+
paddingLeft: 1,
|
|
1960
|
+
backgroundColor: '#000000',
|
|
1961
|
+
},
|
|
1962
|
+
contentOptions: {
|
|
1963
|
+
backgroundColor: '#000000',
|
|
1964
|
+
width: '100%', // Fill container width for proper text wrapping
|
|
1965
|
+
},
|
|
1966
|
+
},
|
|
1916
1967
|
});
|
|
1917
1968
|
|
|
1918
|
-
|
|
1969
|
+
// Show scrollbar when paused, hide when not paused
|
|
1970
|
+
if (outputBox.verticalScrollBar) {
|
|
1971
|
+
outputBox.verticalScrollBar.width = this.isPaused ? 1 : 0;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// Store ScrollBox reference for this pane
|
|
1975
|
+
this.paneScrollBoxes.set(pane.id, outputBox);
|
|
1976
|
+
|
|
1977
|
+
this.buildPaneOutput(pane, outputBox.content, height);
|
|
1978
|
+
|
|
1979
|
+
// Restore or set scroll position immediately
|
|
1980
|
+
if (outputBox && outputBox.scrollTo) {
|
|
1981
|
+
if (this.isPaused && this.paneScrollPositions.has(pane.id)) {
|
|
1982
|
+
// Restore saved scroll position when paused
|
|
1983
|
+
const savedPos = this.paneScrollPositions.get(pane.id);
|
|
1984
|
+
outputBox.scrollTo(savedPos);
|
|
1985
|
+
} else if (!this.isPaused) {
|
|
1986
|
+
// Auto-scroll to bottom when not paused
|
|
1987
|
+
outputBox.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1919
1990
|
|
|
1920
1991
|
paneContainer.add(outputBox);
|
|
1921
1992
|
return paneContainer;
|
|
@@ -2011,6 +2082,16 @@ class ProcessManager {
|
|
|
2011
2082
|
}
|
|
2012
2083
|
|
|
2013
2084
|
buildRunningUI() {
|
|
2085
|
+
// Save scroll positions before destroying
|
|
2086
|
+
for (const [paneId, scrollBox] of this.paneScrollBoxes.entries()) {
|
|
2087
|
+
if (scrollBox) {
|
|
2088
|
+
this.paneScrollPositions.set(paneId, {
|
|
2089
|
+
x: scrollBox.scrollLeft || 0,
|
|
2090
|
+
y: scrollBox.scrollTop || 0,
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2014
2095
|
// Remove old containers if they exist - use destroyRecursively to clean up all children
|
|
2015
2096
|
if (this.selectionContainer) {
|
|
2016
2097
|
this.renderer.root.remove(this.selectionContainer);
|
|
@@ -2029,8 +2110,9 @@ class ProcessManager {
|
|
|
2029
2110
|
this.runningContainer.destroyRecursively();
|
|
2030
2111
|
this.runningContainer = null;
|
|
2031
2112
|
}
|
|
2032
|
-
// Clear outputBox reference since
|
|
2113
|
+
// Clear outputBox reference and scrollbox map since they were destroyed
|
|
2033
2114
|
this.outputBox = null;
|
|
2115
|
+
this.paneScrollBoxes.clear();
|
|
2034
2116
|
|
|
2035
2117
|
// Create main container - full screen with black background
|
|
2036
2118
|
const mainContainer = new BoxRenderable(this.renderer, {
|
|
@@ -2232,6 +2314,7 @@ async function main() {
|
|
|
2232
2314
|
}
|
|
2233
2315
|
|
|
2234
2316
|
const renderer = await createCliRenderer();
|
|
2317
|
+
renderer.start(); // Start the automatic render loop
|
|
2235
2318
|
const manager = new ProcessManager(renderer, scripts);
|
|
2236
2319
|
|
|
2237
2320
|
// Handle cleanup on exit
|