From c5b716c7da6e702ed3c5224906d087f09d258da3 Mon Sep 17 00:00:00 2001 From: Maciej Zientarski Date: Tue, 4 Apr 2023 00:30:53 +0200 Subject: [PATCH 1/2] #1991: add support for 'clear screen' and 'move to home' --- .../browser/serial/monitor/monitor-utils.ts | 90 +++++++--- .../monitor/serial-monitor-send-output.tsx | 44 +++-- .../src/test/browser/monitor-utils.test.ts | 162 +++++++++++++++++- 3 files changed, 245 insertions(+), 51 deletions(-) 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..d11292769 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,81 @@ -import { Line, SerialMonitorOutput } from './serial-monitor-send-output'; +import {Line, SerialMonitorOutput} from './serial-monitor-send-output'; + +function writeOverLine(line: Line, insert: string, cursorPosition: number): [number, number] { + var 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 [line.lineLen - lenBefore, cursorPosition]; +} + +const escapeSequenceGoHome = '\x1B[H'; +const escapeSequenceClearScreen = '\x1B[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; - for (const message of messages) { - const messageLen = message.length; - charCount += messageLen; - const lastLine = linesToAdd[linesToAdd.length - 1]; + let allMessages = messages.join(''); + let overflow = null; - // 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 (allMessages.indexOf(escapeSequenceGoHome) >= 0) { + const before = allMessages.substring(0, allMessages.indexOf(escapeSequenceGoHome)); + const after = allMessages.substring(allMessages.indexOf(escapeSequenceGoHome) + escapeSequenceGoHome.length); + const [_lines, _charCount] = messagesToLines([before], prevLines, charCount, currentLineIndex, currentCursorPosition, separator); + return messagesToLines([after], _lines, _charCount, 0, 0, separator); + } else if (allMessages.indexOf(escapeSequenceClearScreen) >= 0) { + const after = allMessages.substring(allMessages.lastIndexOf(escapeSequenceClearScreen) + escapeSequenceClearScreen.length); + return messagesToLines([after], [], 0, 0, 0, separator); + } else if (allMessages.lastIndexOf('\x1B') >= 0) { + overflow = allMessages.substring(allMessages.lastIndexOf('\x1B')); + const result = messagesToLines([allMessages.substring(0, allMessages.lastIndexOf('\x1B'))], 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()}); } + let [_addedCharacters, _currentCursorPosition] = writeOverLine(prevLines[currentLineIndex], chunk, currentCursorPosition) + charCount += _addedCharacters; + currentCursorPosition = _currentCursorPosition; + } + + if (i < chunks.length - 1) { + let [_addedCharacters, _currentCursorPosition] = writeOverLine(prevLines[currentLineIndex], separator, currentCursorPosition) + charCount += _addedCharacters; + currentCursorPosition = _currentCursorPosition; } } - 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 +99,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..f414bd12e 100644 --- a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts +++ b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts @@ -15,17 +15,20 @@ type TestLine = { charCount: number; maxCharacters?: number; }; + lineIndex?: number; + cursorPosition?: number; + overflow?: string; }; const date = new Date(); const testLines: TestLine[] = [ { messages: ['Hello'], - expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 }, + expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 } }, { messages: ['Hello', 'Dog!'], - expected: { lines: [{ message: 'HelloDog!', lineLen: 9 }], charCount: 9 }, + expected: { lines: [{ message: 'HelloDog!', lineLen: 9 }], charCount: 9 } }, { messages: ['Hello\n', 'Dog!'], @@ -35,7 +38,7 @@ const testLines: TestLine[] = [ { message: 'Dog!', lineLen: 4 }, ], charCount: 10, - }, + } }, { messages: ['Dog!'], @@ -46,7 +49,7 @@ const testLines: TestLine[] = [ { message: 'Dog!', lineLen: 4 }, ], charCount: 10, - }, + } }, { messages: [' Dog!\n', " Who's a good ", 'boy?\n', "You're a good boy!"], @@ -66,7 +69,7 @@ const testLines: TestLine[] = [ { message: '?\n', lineLen: 2 }, { message: "You're a good boy!", lineLen: 8 }, ], - }, + } }, { messages: ['boy?\n', "You're a good boy!"], @@ -92,7 +95,7 @@ const testLines: TestLine[] = [ { message: '?\n', lineLen: 2 }, { message: "You're a good boy!", lineLen: 8 }, ], - }, + } }, { messages: ["Who's a good boy?\n", 'Yo'], @@ -115,7 +118,138 @@ 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 +271,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 +295,8 @@ describe('Monitor Utils', () => { const [truncatedLines, totalCharCount] = truncateLines( newLines, addedCharCount, + cLineIndex, + cCursorPosition, testLine.expectedTruncated?.maxCharacters ); let charCount = 0; From 3e96f7273e8761315167eb2d75cb71c258f6518a Mon Sep 17 00:00:00 2001 From: Maciej Zientarski Date: Tue, 4 Apr 2023 11:29:10 +0200 Subject: [PATCH 2/2] #1991: code cleanup --- .../browser/serial/monitor/monitor-utils.ts | 41 ++++++++++--------- .../src/test/browser/monitor-utils.test.ts | 13 +++--- 2 files changed, 27 insertions(+), 27 deletions(-) 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 d11292769..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,15 +1,16 @@ import {Line, SerialMonitorOutput} from './serial-monitor-send-output'; -function writeOverLine(line: Line, insert: string, cursorPosition: number): [number, number] { - var lenBefore = line.message.length; +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 [line.lineLen - lenBefore, cursorPosition]; + return [charCount + (line.lineLen - lenBefore), cursorPosition]; } -const escapeSequenceGoHome = '\x1B[H'; -const escapeSequenceClearScreen = '\x1B[2J'; +const escape = '\x1B'; +const escapeSequenceGoHome = escape+'[H'; +const escapeSequenceClearScreen = escape+'[2J'; export function messagesToLines( messages: string[], @@ -28,17 +29,21 @@ export function messagesToLines( let allMessages = messages.join(''); let overflow = null; - if (allMessages.indexOf(escapeSequenceGoHome) >= 0) { - const before = allMessages.substring(0, allMessages.indexOf(escapeSequenceGoHome)); - const after = allMessages.substring(allMessages.indexOf(escapeSequenceGoHome) + escapeSequenceGoHome.length); - const [_lines, _charCount] = messagesToLines([before], prevLines, charCount, currentLineIndex, currentCursorPosition, separator); - return messagesToLines([after], _lines, _charCount, 0, 0, separator); - } else if (allMessages.indexOf(escapeSequenceClearScreen) >= 0) { + let goHomeSequenceIndex = allMessages.indexOf(escapeSequenceGoHome); + let clearScreenSequenceIndex = allMessages.indexOf(escapeSequenceClearScreen); + let lastEscapeIndex = allMessages.lastIndexOf(escape); + + 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 (allMessages.lastIndexOf('\x1B') >= 0) { - overflow = allMessages.substring(allMessages.lastIndexOf('\x1B')); - const result = messagesToLines([allMessages.substring(0, allMessages.lastIndexOf('\x1B'))], prevLines, charCount, currentLineIndex, currentCursorPosition, 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; } @@ -54,15 +59,11 @@ export function messagesToLines( if (currentLineIndex > prevLines.length - 1) { prevLines.push({message: '', lineLen: 0, timestamp: new Date()}); } - let [_addedCharacters, _currentCursorPosition] = writeOverLine(prevLines[currentLineIndex], chunk, currentCursorPosition) - charCount += _addedCharacters; - currentCursorPosition = _currentCursorPosition; + [charCount, currentCursorPosition] = writeOverLine(prevLines[currentLineIndex], chunk, currentCursorPosition, charCount) } if (i < chunks.length - 1) { - let [_addedCharacters, _currentCursorPosition] = writeOverLine(prevLines[currentLineIndex], separator, currentCursorPosition) - charCount += _addedCharacters; - currentCursorPosition = _currentCursorPosition; + [charCount, currentCursorPosition] = writeOverLine(prevLines[currentLineIndex], separator, currentCursorPosition, charCount) } } 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 f414bd12e..4d4ada6fd 100644 --- a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts +++ b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts @@ -24,11 +24,11 @@ const date = new Date(); const testLines: TestLine[] = [ { messages: ['Hello'], - expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 } + expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 }, }, { messages: ['Hello', 'Dog!'], - expected: { lines: [{ message: 'HelloDog!', lineLen: 9 }], charCount: 9 } + expected: { lines: [{ message: 'HelloDog!', lineLen: 9 }], charCount: 9 }, }, { messages: ['Hello\n', 'Dog!'], @@ -38,7 +38,7 @@ const testLines: TestLine[] = [ { message: 'Dog!', lineLen: 4 }, ], charCount: 10, - } + }, }, { messages: ['Dog!'], @@ -49,7 +49,7 @@ const testLines: TestLine[] = [ { message: 'Dog!', lineLen: 4 }, ], charCount: 10, - } + }, }, { messages: [' Dog!\n', " Who's a good ", 'boy?\n', "You're a good boy!"], @@ -69,7 +69,7 @@ const testLines: TestLine[] = [ { message: '?\n', lineLen: 2 }, { message: "You're a good boy!", lineLen: 8 }, ], - } + }, }, { messages: ['boy?\n', "You're a good boy!"], @@ -95,7 +95,7 @@ const testLines: TestLine[] = [ { message: '?\n', lineLen: 2 }, { message: "You're a good boy!", lineLen: 8 }, ], - } + }, }, { messages: ["Who's a good boy?\n", 'Yo'], @@ -120,7 +120,6 @@ const testLines: TestLine[] = [ ], } }, - { messages: ['Dog!'], prevLines: { lines: [{ message: 'Hello\n', lineLen: 6 }], charCount: 6 }, 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