diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts index 41cb4f450..48d8c0996 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts @@ -1,47 +1,82 @@ -import { Line, SerialMonitorOutput } from './serial-monitor-send-output'; +import {Line, SerialMonitorOutput} from './serial-monitor-send-output'; + +function writeOverLine(line: Line, insert: string, cursorPosition: number, charCount: number): [number, number] { + const lenBefore = line.message.length; + line.message = line.message.substring(0, cursorPosition) + insert + line.message.substring(cursorPosition + insert.length) + cursorPosition = cursorPosition + insert.length; + line.lineLen = line.message.length; + return [charCount + (line.lineLen - lenBefore), cursorPosition]; +} + +const escape = '\x1B'; +const escapeSequenceGoHome = escape+'[H'; +const escapeSequenceClearScreen = escape+'[2J'; export function messagesToLines( messages: string[], prevLines: Line[] = [], charCount = 0, - separator = '\n' -): [Line[], number] { - const linesToAdd: Line[] = prevLines.length - ? [prevLines[prevLines.length - 1]] - : [{ message: '', lineLen: 0 }]; - if (!(Symbol.iterator in Object(messages))) return [prevLines, charCount]; + currentLineIndex: number | null, + currentCursorPosition: number, + separator = '\n', +): [Line[], number, number | null, number, string | null] { + if (!prevLines.length) { + prevLines = [{message: '', lineLen: 0, timestamp: new Date()}]; + } + + currentLineIndex = currentLineIndex || 0; + + let allMessages = messages.join(''); + let overflow = null; - for (const message of messages) { - const messageLen = message.length; - charCount += messageLen; - const lastLine = linesToAdd[linesToAdd.length - 1]; + let goHomeSequenceIndex = allMessages.indexOf(escapeSequenceGoHome); + let clearScreenSequenceIndex = allMessages.indexOf(escapeSequenceClearScreen); + let lastEscapeIndex = allMessages.lastIndexOf(escape); - // if the previous messages ends with "separator" add a new line - if (lastLine.message.charAt(lastLine.message.length - 1) === separator) { - linesToAdd.push({ - message, - timestamp: new Date(), - lineLen: messageLen, - }); - } else { - // concatenate to the last line - linesToAdd[linesToAdd.length - 1].message += message; - linesToAdd[linesToAdd.length - 1].lineLen += messageLen; - if (!linesToAdd[linesToAdd.length - 1].timestamp) { - linesToAdd[linesToAdd.length - 1].timestamp = new Date(); + if (goHomeSequenceIndex >= 0) { + const before = allMessages.substring(0, goHomeSequenceIndex); + const after = allMessages.substring(goHomeSequenceIndex + escapeSequenceGoHome.length); + const [updatedLines, updatedCharCount] = messagesToLines([before], prevLines, charCount, currentLineIndex, currentCursorPosition, separator); + return messagesToLines([after], updatedLines, updatedCharCount, 0, 0, separator); + } else if (clearScreenSequenceIndex >= 0) { + const after = allMessages.substring(allMessages.lastIndexOf(escapeSequenceClearScreen) + escapeSequenceClearScreen.length); + return messagesToLines([after], [], 0, 0, 0, separator); + } else if (lastEscapeIndex >= 0) { + overflow = allMessages.substring(lastEscapeIndex); + const result = messagesToLines([allMessages.substring(0, lastEscapeIndex)], prevLines, charCount, currentLineIndex, currentCursorPosition, separator); + result[4] = overflow; + return result; + } + + const chunks = allMessages.split(separator); + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + if (chunk !== '') { + if (prevLines[currentLineIndex].message[currentCursorPosition - 1] === '\n') { + currentLineIndex++; + currentCursorPosition = 0; + } + if (currentLineIndex > prevLines.length - 1) { + prevLines.push({message: '', lineLen: 0, timestamp: new Date()}); } + [charCount, currentCursorPosition] = writeOverLine(prevLines[currentLineIndex], chunk, currentCursorPosition, charCount) + } + + if (i < chunks.length - 1) { + [charCount, currentCursorPosition] = writeOverLine(prevLines[currentLineIndex], separator, currentCursorPosition, charCount) } } - prevLines.splice(prevLines.length - 1, 1, ...linesToAdd); - return [prevLines, charCount]; + return [prevLines, charCount, currentLineIndex, currentCursorPosition, overflow] } export function truncateLines( lines: Line[], charCount: number, + currentLineIndex: number | null, + currentCursorPosition: number, maxCharacters: number = SerialMonitorOutput.MAX_CHARACTERS -): [Line[], number] { +): [Line[], number, number | null, number] { let charsToDelete = charCount - maxCharacters; let lineIndex = 0; while (charsToDelete > 0 || lineIndex > 0) { @@ -65,5 +100,5 @@ export function truncateLines( charsToDelete -= deletedCharsCount; lines[0].message = newFirstLine; } - return [lines, charCount]; + return [lines, charCount, currentLineIndex, currentCursorPosition]; } diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx index 2ddd2c565..df3c7fb48 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx @@ -26,6 +26,9 @@ export class SerialMonitorOutput extends React.Component< lines: [], timestamp: this.props.monitorModel.timestamp, charCount: 0, + lineIndex: null, + cursorPosition: 0, + overflow: null }; } @@ -57,22 +60,32 @@ export class SerialMonitorOutput extends React.Component< this.scrollToBottom(); this.toDisposeBeforeUnmount.pushAll([ this.props.monitorManagerProxy.onMessagesReceived(({ messages }) => { - const [newLines, totalCharCount] = messagesToLines( - messages, - this.state.lines, - this.state.charCount - ); - const [lines, charCount] = truncateLines(newLines, totalCharCount); - this.setState( - { - lines, - charCount, - }, - () => this.scrollToBottom() - ); + if(Symbol.iterator in Object(messages)) { + if (this.state.overflow) { + messages[0] = this.state.overflow + messages[0]; + } + const [newLines, totalCharCount, cLineIndex, cCursorPosition, overflow] = messagesToLines( + messages, + this.state.lines, + this.state.charCount, + this.state.lineIndex, + this.state.cursorPosition, + ); + const [lines, charCount, lineIndex, cursorPosition] = truncateLines(newLines, totalCharCount, cLineIndex, cCursorPosition); + this.setState( + { + lines, + charCount, + lineIndex, + cursorPosition, + overflow + }, + () => this.scrollToBottom() + ); + } }), this.props.clearConsoleEvent(() => - this.setState({ lines: [], charCount: 0 }) + this.setState({ lines: [], charCount: 0, lineIndex: null, cursorPosition: 0, overflow: null }) ), this.props.monitorModel.onChange(({ property }) => { if (property === 'timestamp') { @@ -137,6 +150,9 @@ export namespace SerialMonitorOutput { lines: Line[]; timestamp: boolean; charCount: number; + lineIndex: number | null; + cursorPosition: number; + overflow: string | null; } export interface SelectOption { diff --git a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts index cf1025740..4d4ada6fd 100644 --- a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts +++ b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts @@ -15,6 +15,9 @@ type TestLine = { charCount: number; maxCharacters?: number; }; + lineIndex?: number; + cursorPosition?: number; + overflow?: string; }; const date = new Date(); @@ -115,7 +118,137 @@ const testLines: TestLine[] = [ { message: "Who's a good boy?\n", lineLen: 18 }, { message: 'Yo', lineLen: 2 }, ], - }, + } + }, + { + messages: ['Dog!'], + prevLines: { lines: [{ message: 'Hello\n', lineLen: 6 }], charCount: 6 }, + expected: { + lines: [ + { message: 'Hello\n', lineLen: 6 }, + { message: 'Dog!', lineLen: 4 }, + ], + charCount: 10, + } + }, + { + messages: ['\n'], + prevLines: { lines: [ + { message: 'Hello', lineLen: 5 }, + ], charCount: 5 }, + expected: { + lines: [ + { message: 'Hello\n', lineLen: 6 }, + ], + charCount: 6, + } + }, + { + messages: ['\n', '\x1B[H', 'Are', '\nYou'], + prevLines: { lines: [ + { message: 'Hello\n', lineLen: 6 }, + { message: 'Dog!\n', lineLen: 5 }, + { message: 'How', lineLen: 3 }, + ], charCount: 14 }, + expected: { + lines: [ + { message: 'Are\no\n', lineLen: 6 }, + { message: 'You!\n', lineLen: 5 }, + { message: 'How\n', lineLen: 4 }, + ], + charCount: 15, + } + }, + { + messages: ['Yes\x1B[HNo'], + prevLines: { lines: [ + { message: 'Hello\n', lineLen: 6 }, + { message: 'Dog!\n', lineLen: 5 }, + { message: 'How', lineLen: 3 }, + ], charCount: 14 }, + cursorPosition: 1, + lineIndex: 2, + expected: { + lines: [ + { message: 'Nollo\n', lineLen: 6 }, + { message: 'Dog!\n', lineLen: 5 }, + { message: 'HYes', lineLen: 4 }, + ], + charCount: 15, + } + }, + { + messages: ['dy', '\x1B', '[H', 'Reset'], + prevLines: { lines: [ + { message: 'Hello\n', lineLen: 6 }, + { message: 'Dog!\n', lineLen: 5 }, + { message: 'How', lineLen: 3 }, + ], charCount: 14 }, + expected: { + lines: [ + { message: 'Reset\n', lineLen: 6 }, + { message: 'Dog!\n', lineLen: 5 }, + { message: 'Howdy', lineLen: 5 }, + ], + charCount: 16, + } + }, + { + messages: ['HReset'], + prevLines: { lines: [ + { message: 'Hello\n', lineLen: 6 }, + { message: 'Dog!\n', lineLen: 5 }, + { message: 'How', lineLen: 3 }, + ], charCount: 14 }, + overflow: '\x1B[', + expected: { + lines: [ + { message: 'Reset\n', lineLen: 6 }, + { message: 'Dog!\n', lineLen: 5 }, + { message: 'How', lineLen: 3 }, + ], + charCount: 14, + } + }, + { + messages: ['\x1B[H', 'Reset', '\x1B[H', 'Me', '\x1B'], + prevLines: { lines: [ + { message: 'Hello', lineLen: 6 }, + ], charCount: 6 }, + expected: { + lines: [ + { message: 'Meset', lineLen: 6 }, + ], + charCount: 6, + } + }, + { + messages: ['HReset', 'Clear \x1B[2J', 'Me'], + prevLines: { lines: [ + { message: 'Hello\n', lineLen: 6 }, + { message: 'Dog!\n', lineLen: 5 }, + { message: 'How', lineLen: 3 }, + ], charCount: 14 }, + overflow: '\x1B[', + expected: { + lines: [ + { message: 'Me', lineLen: 2 }, + ], + charCount: 2, + } + }, + { + messages: ['2JReset'], + prevLines: { lines: [ + { message: 'How', lineLen: 3 }, + ], charCount: 3 }, + overflow: '\x1B[', + expected: { + lines: [ + { message: 'Reset', lineLen: 5 }, + ], + charCount: 5, + } }, ]; @@ -137,10 +270,18 @@ describe('Monitor Utils', () => { testLines.forEach((testLine) => { context('when converting messages', () => { it('should give the right result', () => { - const [newLines, addedCharCount] = messagesToLines( + const lineIndex = testLine.lineIndex || testLine.prevLines ? testLine.prevLines!.lines.length - 1 : null + const cursorPosition = testLine.cursorPosition || testLine.prevLines?.lines[testLine.prevLines?.lines.length - 1].message.length || 0; + + if (testLine.overflow) { + testLine.messages[0] = testLine.overflow + testLine.messages[0] + } + const [newLines, addedCharCount, cLineIndex, cCursorPosition] = messagesToLines( testLine.messages, testLine.prevLines?.lines, - testLine.prevLines?.charCount + testLine.prevLines?.charCount, + lineIndex, + cursorPosition ); newLines.forEach((line, index) => { expect(line.message).to.equal(testLine.expected.lines[index].message); @@ -153,6 +294,8 @@ describe('Monitor Utils', () => { const [truncatedLines, totalCharCount] = truncateLines( newLines, addedCharCount, + cLineIndex, + cCursorPosition, testLine.expectedTruncated?.maxCharacters ); let charCount = 0; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy