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.
Files changed (2) hide show
  1. package/index.js +122 -39
  2. 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
- // Throttle renders to ~60fps to reduce CPU usage
748
- if (this.renderScheduled) return;
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
- }, 16);
756
+ }
754
757
  }
755
758
 
756
759
  stopProcess(scriptName) {
@@ -1811,9 +1814,58 @@ class ProcessManager {
1811
1814
  }
1812
1815
 
1813
1816
  updateRunningUI() {
1814
- // Just rebuild the entire UI - simpler and more reliable
1815
- // OpenTUI doesn't have great incremental update support anyway
1816
- this.buildRunningUI();
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
- // Calculate visible lines - use global pause state
1825
- const outputHeight = Math.max(3, height - 2);
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 in reverse order (newest first)
1829
- for (let i = linesToShow.length - 1; i >= 0; 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
- // No truncation - let OpenTUI handle text wrapping naturally
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}]`)} ${line.text}`,
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 BoxRenderable that fills remaining space
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 BoxRenderable(this.renderer, {
1948
+ const outputBox = new ScrollBoxRenderable(this.renderer, {
1908
1949
  id: `pane-output-${pane.id}`,
1909
- flexDirection: 'column',
1910
- flexGrow: 1,
1911
- flexShrink: 1,
1912
- flexBasis: 0,
1913
- overflow: 'hidden',
1914
- paddingLeft: 1,
1915
- backgroundColor: '#000000', // Black background for pane
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
- this.buildPaneOutput(pane, outputBox, height);
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 it was destroyed with runningContainer
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "startall",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {