Update devcontainer configuration and enhance terminal handling in InkApp. Change base image to 'devpit:latest' and implement a blinking cursor feature in the terminal display, improving user experience and visual feedback during input.
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"image": "mcr.microsoft.com/devcontainers/javascript-node"
|
"image": "devpit:latest"
|
||||||
}
|
}
|
||||||
@@ -35,19 +35,24 @@ class TerminalService extends EventEmitter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.ptyProcess.onData((data) => {
|
this.ptyProcess.onData((data) => {
|
||||||
// Normalize line endings
|
const str = String(data);
|
||||||
const normalized = String(data).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
for (let i = 0; i < str.length; i += 1) {
|
||||||
const parts = normalized.split('\n');
|
const ch = str[i];
|
||||||
|
if (ch === '\\n') {
|
||||||
// First part joins the existing partial
|
// Line feed completes the current line
|
||||||
if (parts.length > 0) {
|
this.lines.push(this.partial);
|
||||||
this.partial += parts[0];
|
this.partial = '';
|
||||||
}
|
} else if (ch === '\\r') {
|
||||||
|
// Carriage return: move to start of line; start overwriting
|
||||||
// For each subsequent part before the last, we have completed lines
|
this.partial = '';
|
||||||
for (let i = 1; i < parts.length; i += 1) {
|
} else if (ch === '\\b' || ch === '\\x7f') {
|
||||||
this.lines.push(this.partial);
|
// Backspace or DEL: remove last char if present
|
||||||
this.partial = parts[i];
|
if (this.partial.length > 0) {
|
||||||
|
this.partial = this.partial.slice(0, -1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.partial += ch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce max lines buffer
|
// Enforce max lines buffer
|
||||||
|
|||||||
@@ -4,6 +4,26 @@ import TextInput from 'ink-text-input';
|
|||||||
import terminalService from '../terminalService.js';
|
import terminalService from '../terminalService.js';
|
||||||
|
|
||||||
class Pane extends React.Component {
|
class Pane extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
cursorVisible: true,
|
||||||
|
};
|
||||||
|
this._cursorTimer = null;
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.showCursor) {
|
||||||
|
this._cursorTimer = setInterval(() => {
|
||||||
|
this.setState((s) => ({ cursorVisible: !s.cursorVisible }));
|
||||||
|
}, typeof this.props.cursorBlinkMs === 'number' && this.props.cursorBlinkMs > 0 ? this.props.cursorBlinkMs : 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this._cursorTimer) {
|
||||||
|
clearInterval(this._cursorTimer);
|
||||||
|
this._cursorTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Strip ANSI escape sequences so width measurement/truncation is accurate
|
// Strip ANSI escape sequences so width measurement/truncation is accurate
|
||||||
stripAnsi(input) {
|
stripAnsi(input) {
|
||||||
if (input == null) return '';
|
if (input == null) return '';
|
||||||
@@ -35,6 +55,26 @@ class Pane extends React.Component {
|
|||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
// Apply a blinking cursor to the given line according to width constraints
|
||||||
|
withCursor(line, maxWidth) {
|
||||||
|
const cursorChar = typeof this.props.cursorChar === 'string' && this.props.cursorChar.length > 0 ? this.props.cursorChar[0] : '█';
|
||||||
|
const visible = !!this.state.cursorVisible;
|
||||||
|
if (!visible) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
if (typeof maxWidth === 'number' && maxWidth > 0) {
|
||||||
|
if (line.length < maxWidth) {
|
||||||
|
return `${line}${cursorChar}`;
|
||||||
|
}
|
||||||
|
if (line.length === maxWidth) {
|
||||||
|
// Replace last char to avoid overflow
|
||||||
|
return `${line.slice(0, maxWidth - 1)}${cursorChar}`;
|
||||||
|
}
|
||||||
|
// If somehow longer, just ensure width
|
||||||
|
return `${line.slice(0, maxWidth - 1)}${cursorChar}`;
|
||||||
|
}
|
||||||
|
return `${line}${cursorChar}`;
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
const { title, lines, maxWidth } = this.props;
|
const { title, lines, maxWidth } = this.props;
|
||||||
return (
|
return (
|
||||||
@@ -42,20 +82,17 @@ class Pane extends React.Component {
|
|||||||
<Text color="cyan">{title}</Text>
|
<Text color="cyan">{title}</Text>
|
||||||
<Box flexDirection="column" width="100%" flexShrink={1} minWidth={0}>
|
<Box flexDirection="column" width="100%" flexShrink={1} minWidth={0}>
|
||||||
{(lines && lines.length > 0)
|
{(lines && lines.length > 0)
|
||||||
? lines.map((line, index) => (
|
? lines.map((line, index) => {
|
||||||
<Text key={index}>{
|
const isLast = index === lines.length - 1;
|
||||||
(() => {
|
const width = typeof maxWidth === 'number' && maxWidth > 0 ? maxWidth : undefined;
|
||||||
const clean = this.stripAnsi(line);
|
const clean = this.stripAnsi(line);
|
||||||
const width = typeof maxWidth === 'number' && maxWidth > 0 ? maxWidth : undefined;
|
const expanded = this.expandTabs(clean, 8, width);
|
||||||
// Expand tabs before slicing to visual width
|
const baseLine = (width && expanded.length > width) ? expanded.slice(0, width) : expanded;
|
||||||
const expanded = this.expandTabs(clean, 8, width);
|
const finalLine = (this.props.showCursor && isLast) ? this.withCursor(baseLine, width) : baseLine;
|
||||||
if (width && expanded.length > width) {
|
return (
|
||||||
return expanded.slice(0, width);
|
<Text key={index}>{finalLine}</Text>
|
||||||
}
|
);
|
||||||
return expanded;
|
})
|
||||||
})()
|
|
||||||
}</Text>
|
|
||||||
))
|
|
||||||
: <Text dimColor>—</Text>
|
: <Text dimColor>—</Text>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -201,7 +238,7 @@ export default class InkApp extends React.Component {
|
|||||||
<Pane title="Chain of Thought" lines={chainOfThoughtView} maxWidth={paneContentWidth} />
|
<Pane title="Chain of Thought" lines={chainOfThoughtView} maxWidth={paneContentWidth} />
|
||||||
</Box>
|
</Box>
|
||||||
<Box flexGrow={1} flexDirection="column" minWidth={0}>
|
<Box flexGrow={1} flexDirection="column" minWidth={0}>
|
||||||
<Pane title="Terminal" lines={terminalView} maxWidth={paneContentWidth} />
|
<Pane title="Terminal" lines={terminalView} maxWidth={paneContentWidth} showCursor cursorBlinkMs={600} />
|
||||||
<Pane title="Logging" lines={logsView} maxWidth={paneContentWidth} />
|
<Pane title="Logging" lines={logsView} maxWidth={paneContentWidth} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user