startall 0.0.9 → 0.0.11
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 +153 -76
- package/package.json +3 -2
package/index.js
CHANGED
|
@@ -357,6 +357,10 @@ class ProcessManager {
|
|
|
357
357
|
this.splitMode = false; // Whether waiting for split command after Ctrl+b
|
|
358
358
|
this.showSplitMenu = false; // Whether to show the command palette
|
|
359
359
|
this.splitMenuIndex = 0; // Selected item in split menu
|
|
360
|
+
this.paneScrollPositions = new Map(); // Store scroll positions per pane ID
|
|
361
|
+
this.paneScrollBoxes = new Map(); // Store ScrollBox references per pane ID
|
|
362
|
+
this.paneFilterState = new Map(); // Track filter state per pane to detect changes
|
|
363
|
+
this.paneLineCount = new Map(); // Track how many lines we've rendered per pane
|
|
360
364
|
|
|
361
365
|
// Assign colors to each script
|
|
362
366
|
this.processColors = new Map();
|
|
@@ -1811,9 +1815,94 @@ class ProcessManager {
|
|
|
1811
1815
|
}
|
|
1812
1816
|
|
|
1813
1817
|
updateRunningUI() {
|
|
1814
|
-
//
|
|
1815
|
-
|
|
1816
|
-
|
|
1818
|
+
// Update existing panes instead of rebuilding everything
|
|
1819
|
+
if (this.paneScrollBoxes.size > 0) {
|
|
1820
|
+
// Update each pane's content
|
|
1821
|
+
for (const [paneId, scrollBox] of this.paneScrollBoxes.entries()) {
|
|
1822
|
+
const pane = findPaneById(this.paneRoot, paneId);
|
|
1823
|
+
if (pane && scrollBox && scrollBox.content) {
|
|
1824
|
+
// Check if filter state changed or if paused (requires rebuild)
|
|
1825
|
+
const currentFilterState = JSON.stringify({
|
|
1826
|
+
filter: pane.filter || '',
|
|
1827
|
+
hidden: pane.hidden || [],
|
|
1828
|
+
processes: pane.processes || [],
|
|
1829
|
+
colorFilter: pane.colorFilter || null,
|
|
1830
|
+
});
|
|
1831
|
+
const previousFilterState = this.paneFilterState.get(paneId);
|
|
1832
|
+
const filterChanged = currentFilterState !== previousFilterState;
|
|
1833
|
+
const needsRebuild = filterChanged || this.isPaused;
|
|
1834
|
+
|
|
1835
|
+
if (needsRebuild) {
|
|
1836
|
+
// Filter changed - need to rebuild all content
|
|
1837
|
+
this.paneFilterState.set(paneId, currentFilterState);
|
|
1838
|
+
|
|
1839
|
+
// Remove all children
|
|
1840
|
+
if (scrollBox.content.children) {
|
|
1841
|
+
while (scrollBox.content.children.length > 0) {
|
|
1842
|
+
const child = scrollBox.content.children[0];
|
|
1843
|
+
if (child && child.id) {
|
|
1844
|
+
scrollBox.content.remove(child.id);
|
|
1845
|
+
} else {
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// Rebuild all content
|
|
1852
|
+
const height = scrollBox.height || this.renderer.height - 6;
|
|
1853
|
+
this.buildPaneOutput(pane, scrollBox.content, height);
|
|
1854
|
+
|
|
1855
|
+
// Update line count after rebuild
|
|
1856
|
+
const lines = this.getOutputLinesForPane(pane);
|
|
1857
|
+
this.paneLineCount.set(paneId, lines.length);
|
|
1858
|
+
|
|
1859
|
+
// Auto-scroll to bottom after filter change
|
|
1860
|
+
if (!this.isPaused) {
|
|
1861
|
+
scrollBox.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
1862
|
+
}
|
|
1863
|
+
} else {
|
|
1864
|
+
// No filter change - just append new lines
|
|
1865
|
+
const lines = this.getOutputLinesForPane(pane);
|
|
1866
|
+
const lastRenderedCount = this.paneLineCount.get(paneId) || 0;
|
|
1867
|
+
|
|
1868
|
+
if (lines.length > lastRenderedCount) {
|
|
1869
|
+
const newLines = lines.slice(lastRenderedCount);
|
|
1870
|
+
|
|
1871
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
1872
|
+
const line = newLines[i];
|
|
1873
|
+
const lineIndex = lastRenderedCount + i;
|
|
1874
|
+
const processColor = this.processColors.get(line.process) || COLORS.text;
|
|
1875
|
+
const trimmedText = line.text.trim();
|
|
1876
|
+
|
|
1877
|
+
const outputLine = new TextRenderable(this.renderer, {
|
|
1878
|
+
id: `output-${pane.id}-${lineIndex}`,
|
|
1879
|
+
content: t`${fg(processColor)(`[${line.process}]`)} ${trimmedText}`,
|
|
1880
|
+
bg: '#000000',
|
|
1881
|
+
});
|
|
1882
|
+
|
|
1883
|
+
scrollBox.content.add(outputLine);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// Update line count
|
|
1887
|
+
this.paneLineCount.set(paneId, lines.length);
|
|
1888
|
+
|
|
1889
|
+
// Auto-scroll to bottom if not paused
|
|
1890
|
+
if (!this.isPaused) {
|
|
1891
|
+
scrollBox.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// Update scrollbar visibility based on pause state
|
|
1897
|
+
if (scrollBox.verticalScrollBar) {
|
|
1898
|
+
scrollBox.verticalScrollBar.width = this.isPaused ? 1 : 0;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
} else {
|
|
1903
|
+
// First time or no panes exist - do full rebuild
|
|
1904
|
+
this.buildRunningUI();
|
|
1905
|
+
}
|
|
1817
1906
|
}
|
|
1818
1907
|
|
|
1819
1908
|
// Build a single pane's output area
|
|
@@ -1821,79 +1910,24 @@ class ProcessManager {
|
|
|
1821
1910
|
const isFocused = pane.id === this.focusedPaneId;
|
|
1822
1911
|
const lines = this.getOutputLinesForPane(pane);
|
|
1823
1912
|
|
|
1824
|
-
//
|
|
1825
|
-
const
|
|
1826
|
-
let linesToShow = this.isPaused ? lines : lines.slice(-outputHeight);
|
|
1827
|
-
|
|
1828
|
-
// Use full terminal width for padding - simpler and ensures complete clearing
|
|
1829
|
-
// Each pane will pad to full width, excess will be clipped by container
|
|
1830
|
-
const approxPaneWidth = this.renderer.width;
|
|
1913
|
+
// Don't slice - show all lines and let ScrollBox handle scrolling
|
|
1914
|
+
const linesToShow = lines;
|
|
1831
1915
|
|
|
1832
|
-
// Add lines
|
|
1833
|
-
for (let i =
|
|
1916
|
+
// Add lines (oldest first, so newest is at bottom)
|
|
1917
|
+
for (let i = 0; i < linesToShow.length; i++) {
|
|
1834
1918
|
const line = linesToShow[i];
|
|
1835
1919
|
const processColor = this.processColors.get(line.process) || COLORS.text;
|
|
1836
1920
|
|
|
1837
|
-
|
|
1838
|
-
const
|
|
1839
|
-
let truncatedText = line.text;
|
|
1840
|
-
if (visibleLength > maxWidth) {
|
|
1841
|
-
let visible = 0;
|
|
1842
|
-
const ansiRegex = /\x1b\[[0-9;]*m/g;
|
|
1843
|
-
let lastIndex = 0;
|
|
1844
|
-
let result = '';
|
|
1845
|
-
let match;
|
|
1846
|
-
const text = line.text;
|
|
1847
|
-
while ((match = ansiRegex.exec(text)) !== null) {
|
|
1848
|
-
const before = text.slice(lastIndex, match.index);
|
|
1849
|
-
for (const char of before) {
|
|
1850
|
-
if (visible >= maxWidth - 3) break;
|
|
1851
|
-
result += char;
|
|
1852
|
-
visible++;
|
|
1853
|
-
}
|
|
1854
|
-
if (visible >= maxWidth - 3) break;
|
|
1855
|
-
result += match[0];
|
|
1856
|
-
lastIndex = ansiRegex.lastIndex;
|
|
1857
|
-
}
|
|
1858
|
-
if (visible < maxWidth - 3) {
|
|
1859
|
-
const remaining = text.slice(lastIndex);
|
|
1860
|
-
for (const char of remaining) {
|
|
1861
|
-
if (visible >= maxWidth - 3) break;
|
|
1862
|
-
result += char;
|
|
1863
|
-
visible++;
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
truncatedText = result + '\x1b[0m...';
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
// Get visible length of current line and pad to fill width
|
|
1870
|
-
const linePrefix = `[${line.process}] `;
|
|
1871
|
-
const currentLength = linePrefix.length + stripAnsi(truncatedText).length;
|
|
1872
|
-
|
|
1873
|
-
// Pad with spaces to fill the width
|
|
1874
|
-
const paddingNeeded = Math.max(0, approxPaneWidth - currentLength);
|
|
1875
|
-
const padding = ' '.repeat(paddingNeeded);
|
|
1876
|
-
|
|
1921
|
+
// Trim whitespace and let text wrap naturally - ScrollBox will handle overflow
|
|
1922
|
+
const trimmedText = line.text.trim();
|
|
1877
1923
|
const outputLine = new TextRenderable(this.renderer, {
|
|
1878
1924
|
id: `output-${pane.id}-${i}`,
|
|
1879
|
-
content: t`${fg(processColor)(`[${line.process}]`)} ${
|
|
1925
|
+
content: t`${fg(processColor)(`[${line.process}]`)} ${trimmedText}`,
|
|
1880
1926
|
bg: '#000000', // Black background for pane content
|
|
1881
1927
|
});
|
|
1882
1928
|
|
|
1883
1929
|
container.add(outputLine);
|
|
1884
1930
|
}
|
|
1885
|
-
|
|
1886
|
-
// Fill remaining vertical space with blank lines
|
|
1887
|
-
const emptyLinesNeeded = Math.max(0, outputHeight - linesToShow.length);
|
|
1888
|
-
for (let j = 0; j < emptyLinesNeeded; j++) {
|
|
1889
|
-
const emptyLine = new TextRenderable(this.renderer, {
|
|
1890
|
-
id: `empty-${pane.id}-${j}`,
|
|
1891
|
-
content: ' '.repeat(approxPaneWidth), // Fill entire width with spaces
|
|
1892
|
-
bg: '#000000', // Black background for empty lines
|
|
1893
|
-
});
|
|
1894
|
-
|
|
1895
|
-
container.add(emptyLine);
|
|
1896
|
-
}
|
|
1897
1931
|
}
|
|
1898
1932
|
|
|
1899
1933
|
// Count how many vertical panes exist (for width calculation)
|
|
@@ -1943,22 +1977,54 @@ class ProcessManager {
|
|
|
1943
1977
|
backgroundColor: '#000000', // Black background for pane container
|
|
1944
1978
|
});
|
|
1945
1979
|
|
|
1946
|
-
// Output content - use
|
|
1980
|
+
// Output content - use ScrollBox to handle text wrapping properly
|
|
1947
1981
|
// Use passed height or calculate default for line count calculation
|
|
1948
1982
|
const height = availableHeight ? Math.max(5, availableHeight - 2) : Math.max(5, this.renderer.height - 6);
|
|
1949
1983
|
|
|
1950
|
-
const outputBox = new
|
|
1984
|
+
const outputBox = new ScrollBoxRenderable(this.renderer, {
|
|
1951
1985
|
id: `pane-output-${pane.id}`,
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1986
|
+
height: height,
|
|
1987
|
+
scrollX: false, // Disable horizontal scrollbar entirely
|
|
1988
|
+
scrollY: true, // Enable vertical scrolling
|
|
1989
|
+
focusable: true, // Enable mouse interactions and keyboard scrolling
|
|
1990
|
+
style: {
|
|
1991
|
+
rootOptions: {
|
|
1992
|
+
flexGrow: 1,
|
|
1993
|
+
flexShrink: 1,
|
|
1994
|
+
flexBasis: 0,
|
|
1995
|
+
paddingLeft: 1,
|
|
1996
|
+
backgroundColor: '#000000',
|
|
1997
|
+
},
|
|
1998
|
+
contentOptions: {
|
|
1999
|
+
backgroundColor: '#000000',
|
|
2000
|
+
width: '100%', // Fill container width for proper text wrapping
|
|
2001
|
+
},
|
|
2002
|
+
},
|
|
1959
2003
|
});
|
|
1960
2004
|
|
|
1961
|
-
|
|
2005
|
+
// Show scrollbar when paused, hide when not paused
|
|
2006
|
+
if (outputBox.verticalScrollBar) {
|
|
2007
|
+
outputBox.verticalScrollBar.width = this.isPaused ? 1 : 0;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// Store ScrollBox reference for this pane
|
|
2011
|
+
this.paneScrollBoxes.set(pane.id, outputBox);
|
|
2012
|
+
|
|
2013
|
+
this.buildPaneOutput(pane, outputBox.content, height);
|
|
2014
|
+
|
|
2015
|
+
// Restore or set scroll position
|
|
2016
|
+
setTimeout(() => {
|
|
2017
|
+
if (!outputBox || !outputBox.scrollTo) return;
|
|
2018
|
+
|
|
2019
|
+
if (this.isPaused && this.paneScrollPositions.has(pane.id)) {
|
|
2020
|
+
// Restore saved scroll position when paused
|
|
2021
|
+
const savedPos = this.paneScrollPositions.get(pane.id);
|
|
2022
|
+
outputBox.scrollTo(savedPos);
|
|
2023
|
+
} else if (!this.isPaused) {
|
|
2024
|
+
// Auto-scroll to bottom when not paused
|
|
2025
|
+
outputBox.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
2026
|
+
}
|
|
2027
|
+
}, 0);
|
|
1962
2028
|
|
|
1963
2029
|
paneContainer.add(outputBox);
|
|
1964
2030
|
return paneContainer;
|
|
@@ -2054,6 +2120,16 @@ class ProcessManager {
|
|
|
2054
2120
|
}
|
|
2055
2121
|
|
|
2056
2122
|
buildRunningUI() {
|
|
2123
|
+
// Save scroll positions before destroying
|
|
2124
|
+
for (const [paneId, scrollBox] of this.paneScrollBoxes.entries()) {
|
|
2125
|
+
if (scrollBox) {
|
|
2126
|
+
this.paneScrollPositions.set(paneId, {
|
|
2127
|
+
x: scrollBox.scrollLeft || 0,
|
|
2128
|
+
y: scrollBox.scrollTop || 0,
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2057
2133
|
// Remove old containers if they exist - use destroyRecursively to clean up all children
|
|
2058
2134
|
if (this.selectionContainer) {
|
|
2059
2135
|
this.renderer.root.remove(this.selectionContainer);
|
|
@@ -2072,8 +2148,9 @@ class ProcessManager {
|
|
|
2072
2148
|
this.runningContainer.destroyRecursively();
|
|
2073
2149
|
this.runningContainer = null;
|
|
2074
2150
|
}
|
|
2075
|
-
// Clear outputBox reference since
|
|
2151
|
+
// Clear outputBox reference and scrollbox map since they were destroyed
|
|
2076
2152
|
this.outputBox = null;
|
|
2153
|
+
this.paneScrollBoxes.clear();
|
|
2077
2154
|
|
|
2078
2155
|
// Create main container - full screen with black background
|
|
2079
2156
|
const mainContainer = new BoxRenderable(this.renderer, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "startall",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"demo:worker": "node -e \"setInterval(() => console.log('Processing jobs...'), 500)\"",
|
|
15
15
|
"demo:worker2": "node -e \"setInterval(() => console.log('Processing jobs...'), 100)\"",
|
|
16
16
|
"demo:errors": "node -e \"setInterval(() => { console.log('\\x1b[32mOK: All good\\x1b[0m'); console.log('\\x1b[33mWARN: Deprecated API used\\x1b[0m'); console.log('\\x1b[31mERROR: Connection failed\\x1b[0m'); console.log('Normal log message'); }, 2000)\"",
|
|
17
|
-
"demo:build": "node -e \"let i=0; setInterval(() => { i++; if(i%5===0) console.log('\\x1b[31mError: Type mismatch in file.ts:42\\x1b[0m'); else if(i%3===0) console.log('\\x1b[33mWarning: Unused variable\\x1b[0m'); else console.log('\\x1b[36mCompiling module ' + i + '...\\x1b[0m'); }, 800)\""
|
|
17
|
+
"demo:build": "node -e \"let i=0; setInterval(() => { i++; if(i%5===0) console.log('\\x1b[31mError: Type mismatch in file.ts:42\\x1b[0m'); else if(i%3===0) console.log('\\x1b[33mWarning: Unused variable\\x1b[0m'); else console.log('\\x1b[36mCompiling module ' + i + '...\\x1b[0m'); }, 800)\"",
|
|
18
|
+
"demo:longtext": "node -e \"setInterval(() => { console.log('\\x1b[36m[2024-01-20T12:34:56.789Z] Executing SQL query: SELECT users.id, users.name, users.email, orders.order_id, orders.total, products.name FROM users INNER JOIN orders ON users.id = orders.user_id LEFT JOIN products ON orders.product_id = products.id WHERE users.created_at > NOW() - INTERVAL 30 DAY AND orders.status = \\'completed\\' ORDER BY orders.total DESC LIMIT 100\\x1b[0m'); console.log('Fetched 42 records in 127ms'); }, 1500)\""
|
|
18
19
|
},
|
|
19
20
|
"keywords": [],
|
|
20
21
|
"author": "",
|