diff --git a/common/modules/mixly-modules/common/editor-ace.js b/common/modules/mixly-modules/common/editor-ace.js index 4c052417..ee8c5133 100644 --- a/common/modules/mixly-modules/common/editor-ace.js +++ b/common/modules/mixly-modules/common/editor-ace.js @@ -98,6 +98,44 @@ class EditorAce extends EditorBase { } } + replaceLine(lineNumber, newContent) { + const session = this.#editor_.getSession(); + const totalLines = session.getLength(); + let targetLine = lineNumber < 0 ? totalLines + lineNumber : lineNumber; + if (targetLine < 0) { + targetLine = 0; + } else if (targetLine >= totalLines) { + targetLine = totalLines - 1; + } + const cursorPos = this.#editor_.getCursorPosition(); + const oldLine = session.getLine(targetLine); + const range = new ace.Range(targetLine, 0, targetLine, oldLine.length); + session.replace(range, newContent); + if (cursorPos.row > targetLine) { + this.#editor_.moveCursorTo(cursorPos.row, cursorPos.column); + } else if (cursorPos.row === targetLine) { + const newCol = Math.min(cursorPos.column, newContent.length); + this.#editor_.moveCursorTo(targetLine, newCol); + } else { + this.#editor_.moveCursorTo(cursorPos.row, cursorPos.column); + } + this.#editor_.renderer.scrollCursorIntoView(null, 0.5); + } + + appendLine(text) { + const session = this.#editor_.getSession(); + const totalLines = session.getLength(); + const lastLineIndex = totalLines - 1; + const lastLineText = session.getLine(lastLineIndex); + if (lastLineText.trim()) { + session.insert({ row: totalLines, column: 0 }, `\n${text}`); + } else { + const range = new ace.Range(lastLineIndex, 0, lastLineIndex, lastLineText.length); + session.replace(range, text); + } + this.scrollToBottom(); + } + getValue() { return this.#destroyed_ ? '' : this.#editor_.getValue(); } diff --git a/common/modules/mixly-modules/deps.json b/common/modules/mixly-modules/deps.json index bc6b59ba..d9736c29 100644 --- a/common/modules/mixly-modules/deps.json +++ b/common/modules/mixly-modules/deps.json @@ -1560,6 +1560,7 @@ "path", "Mustache", "Mixly.Env", + "Mixly.Events", "Mixly.Msg", "Mixly.Ampy", "Mixly.Web" diff --git a/common/modules/mixly-modules/web/ampy.js b/common/modules/mixly-modules/web/ampy.js index 42de7be1..19249ff5 100644 --- a/common/modules/mixly-modules/web/ampy.js +++ b/common/modules/mixly-modules/web/ampy.js @@ -3,6 +3,7 @@ goog.loadJs('web', () => { goog.require('path'); goog.require('Mustache'); goog.require('Mixly.Env'); +goog.require('Mixly.Events'); goog.require('Mixly.Msg'); goog.require('Mixly.Ampy'); goog.require('Mixly.Web'); @@ -10,6 +11,7 @@ goog.provide('Mixly.Web.Ampy'); const { Env, + Events, Msg, Ampy, Web @@ -37,6 +39,7 @@ class AmpyExt extends Ampy { #writeBuffer_ = true; #active_ = false; #dataLength_ = 256; + #events_ = new Events(['message', 'replaceMessage']) constructor(device, writeBuffer = true, dataLength = 256) { super(); this.#device_ = device; @@ -56,6 +59,25 @@ class AmpyExt extends Ampy { }); } + bind(...args) { + return this.#events_.bind(...args); + } + + message(message) { + this.#events_.run('message', message); + } + + replaceMessage(lineNumber, message) { + this.#events_.run('replaceMessage', lineNumber, message); + } + + getProgressMessage(name, percent) { + const sended = parseInt(percent * 45); + const left = percent === 0 ? '' : Array(sended).fill('=').join(''); + const right = percent === 100 ? '' : Array(45 - sended).fill('-').join(''); + return `${name} → |${left}${right}| ${(percent * 100).toFixed(1)}%`; + } + isActive() { return this.#active_; } @@ -235,6 +257,8 @@ class AmpyExt extends Ampy { if (!this.isActive()) { throw new Error(Msg.Lang['ampy.portIsNotOpen']); } + this.message(`Writing ${filename}...\n`); + this.message(this.getProgressMessage('', 0)); await this.exec(`file = open('${filename}', 'wb')`, timeout); let buffer = null; if (data.constructor === String) { @@ -245,8 +269,15 @@ class AmpyExt extends Ampy { buffer = data; } const len = Math.ceil(buffer.length / 64); + if (!len) { + this.replaceMessage(-1, this.getProgressMessage('', 1)); + } + let sendedLength = 0; for (let i = 0; i < len; i++) { const writeBuffer = buffer.slice(i * 64, Math.min((i + 1) * 64, buffer.length)); + sendedLength += writeBuffer.length; + const percent = sendedLength / buffer.length; + this.replaceMessage(-1, this.getProgressMessage('', percent)); let writeStr = ''; for (let num of writeBuffer) { let numStr = num.toString(16); @@ -258,6 +289,7 @@ class AmpyExt extends Ampy { await this.exec(`file.write(b'${writeStr}')`, timeout); } await this.exec('file.close()', timeout); + this.message('\n'); } async ls(directory = '/', longFormat = true, recursive = false, timeout = 5000) { @@ -387,6 +419,8 @@ class AmpyExt extends Ampy { this.#active_ = false; await this.#device_.dispose(); this.#device_ = null; + this.#events_.reset(); + this.#events_ = null; } } diff --git a/common/modules/mixly-modules/web/burn-upload.js b/common/modules/mixly-modules/web/burn-upload.js index 9f703200..c0cfb5e7 100644 --- a/common/modules/mixly-modules/web/burn-upload.js +++ b/common/modules/mixly-modules/web/burn-upload.js @@ -418,9 +418,7 @@ BU.burnWithKFlash = async (binFile, erase) => { mainStatusBarTabs.changeTo('output'); BU.progressLayer.title(`${Msg.Lang['shell.burning']}...`); BU.progressLayer.show(); - statusBarTerminal.addValue(Msg.Lang['shell.bin.reading'] + "..."); - statusBarTerminal.addValue("\n"); - + let data = []; try { for (let i of binFile) { @@ -437,30 +435,32 @@ BU.burnWithKFlash = async (binFile, erase) => { } } } catch (error) { - statusBarTerminal.addValue("Failed!\n" + Msg.Lang['shell.bin.readFailed'] + "!\n"); - statusBarTerminal.addValue("\n" + error + "\n", true); + statusBarTerminal.addValue(`\n[ERROR] ${error}\n`, true); BU.progressLayer.hide(); statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`); return; } - statusBarTerminal.addValue("Done!\n"); let serial = null; try { serial = new Serial(portName); - const kflash = new KFlash(serial, (message) => { + const kflash = new KFlash(serial); + kflash.bind('message', (message) => { statusBarTerminal.addValue(message); }); + kflash.bind('replaceMessage', (lineNumber, message) => { + statusBarTerminal.replaceLine(lineNumber, message); + }); await kflash.enter(); for (let item of data) { await kflash.write(item.data, item.address, item.sha256Prefix ?? true, item?.filename ?? 'main.bin'); } BU.progressLayer.hide(); layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 }); - statusBarTerminal.addValue(`==${Msg.Lang['shell.burnSucc']}==\n`); + statusBarTerminal.appendLine(`==${Msg.Lang['shell.burnSucc']}==\n`); } catch (error) { - statusBarTerminal.addValue(`[ERROR] ${error.message}\n`); + statusBarTerminal.appendLine(`[ERROR] ${error.message}\n`); BU.progressLayer.hide(); - statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`); + statusBarTerminal.appendLine(`==${Msg.Lang['shell.burnFailed']}==\n`); } finally { try { serial && await serial.close(); @@ -640,7 +640,7 @@ BU.uploadWithAmpy = async (portName) => { let statusBarSerial = mainStatusBarTabs.getStatusBarById(portName); BU.burning = false; BU.uploading = true; - statusBarTerminal.setValue(Msg.Lang['shell.uploading'] + '...\n'); + statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`); mainStatusBarTabs.show(); mainStatusBarTabs.changeTo('output'); const mainWorkspace = Workspace.getMain(); @@ -650,12 +650,21 @@ BU.uploadWithAmpy = async (portName) => { BU.progressLayer.show(); const serial = new Serial(portName); const ampy = new Ampy(serial); + ampy.bind('message', (message) => { + statusBarTerminal.addValue(message); + }); + ampy.bind('replaceMessage', (lineNumber, message) => { + statusBarTerminal.replaceLine(lineNumber, message); + }); const code = editor.getCode(); let closePromise = Promise.resolve(); if (statusBarSerial) { closePromise = statusBarSerial.close(); } try { + await closePromise; + await ampy.enter(); + await ampy.put('main.py', code); /*const importsMap = BU.getImportModules(code); let libraries = {}; for (let key in importsMap) { @@ -665,13 +674,8 @@ BU.uploadWithAmpy = async (portName) => { data, size: importsMap[key]['__size__'] }; - }*/ - await closePromise; - await ampy.enter(); - statusBarTerminal.addValue('Writing main.py '); - await ampy.put('main.py', code); - statusBarTerminal.addValue('Done!\n'); - /*const cwd = await ampy.cwd(); + } + let cwd = await ampy.cwd(); const rootInfo = await ampy.ls(cwd); let rootMap = {}; for (let item of rootInfo) { @@ -683,19 +687,17 @@ BU.uploadWithAmpy = async (portName) => { if (libraries && libraries instanceof Object) { for (let key in libraries) { if (rootMap[`${cwd}/${key}`] !== undefined && rootMap[`${cwd}/${key}`] === libraries[key].size) { - statusBarTerminal.addValue(`Skip ${key}\n`); + statusBarTerminal.addValue(`Writing ${key} (Skipped)\n`); continue; } - statusBarTerminal.addValue(`Writing ${key} `); await ampy.put(key, libraries[key].data); - statusBarTerminal.addValue('Done!\n'); } }*/ await ampy.exit(); await ampy.dispose(); BU.progressLayer.hide(); layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 }); - statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadSucc']}==\n`); + statusBarTerminal.appendLine(`==${Msg.Lang['shell.uploadSucc']}==\n`); if (!statusBarSerial) { mainStatusBarTabs.add('serial', portName); statusBarSerial = mainStatusBarTabs.getStatusBarById(portName); @@ -707,8 +709,8 @@ BU.uploadWithAmpy = async (portName) => { ampy.dispose(); BU.progressLayer.hide(); Debug.error(error); - statusBarTerminal.addValue(`${error}\n`); - statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`); + statusBarTerminal.appendLine(`[ERROR] ${error.message}\n`); + statusBarTerminal.appendLine(`==${Msg.Lang['shell.uploadFailed']}==\n`); } } diff --git a/common/modules/mixly-modules/web/kflash.js b/common/modules/mixly-modules/web/kflash.js index 4f73cd5a..40488af9 100644 --- a/common/modules/mixly-modules/web/kflash.js +++ b/common/modules/mixly-modules/web/kflash.js @@ -100,7 +100,7 @@ class MAIXLoader { #port_ = null; #readedBuffer_ = []; - #events_ = new Events('message'); + #events_ = new Events(['message', 'replaceMessage']); constructor(port) { this.#port_ = port; @@ -117,6 +117,10 @@ class MAIXLoader { this.#events_.run('message', message); } + replaceMessage(lineNumber, message) { + this.#events_.run('replaceMessage', lineNumber, message); + } + async write(packet) { let handlePacket = []; @@ -251,9 +255,9 @@ class MAIXLoader { getProgressMessage(name, percent) { const sended = parseInt(percent * 45); - const left = Array(sended).fill('=').join(''); - const right = Array(45 - sended).fill('-').join(''); - return `${name}: |${left}${right}| ${(percent * 100).toFixed(1)}%\n\n`; + const left = percent === 0 ? '' : Array(sended).fill('=').join(''); + const right = percent === 100 ? '' : Array(45 - sended).fill('-').join(''); + return `${name}: |${left}${right}| ${(percent * 100).toFixed(1)}%`; } async flashDataFrame(data, address = 0x80000000) { @@ -261,6 +265,7 @@ class MAIXLoader { const dataLength = data.length; let sendedLength = 0; const len = Math.ceil(dataLength / this.DATAFRAME_SIZE); + this.message('\n'); for (let i = 0; i < len; i++) { let start = i * this.DATAFRAME_SIZE; let end = Math.min(start + this.DATAFRAME_SIZE, dataLength); @@ -286,12 +291,18 @@ class MAIXLoader { await this.write(packet); if (await this.recvDebug()) { sendedLength += chunk.length; - this.message(this.getProgressMessage('Downloading ISP', sendedLength / dataLength)); + const message = this.getProgressMessage('Downloading ISP', sendedLength / dataLength); + if (i > 0) { + this.replaceMessage(-1, message); + } else { + this.message(message); + } address += this.DATAFRAME_SIZE; break; } } } + this.message('\n\n'); } async installFlashBootloader(data) { @@ -390,7 +401,7 @@ class MAIXLoader { } async flashFirmware(firmware, aesKey = null, address = 0, sha256Prefix = true, filename = '') { - this.message(`[INFO] Writing ${filename} into ${this.formatHex(address)}\n\n`); + this.message(`[INFO] Writing ${filename} into ${this.formatHex(address)}\n`); let firmwareBin = Array.from(new Uint8Array(firmware)); if (sha256Prefix) { const aesCipherFlag = 0x00; @@ -432,18 +443,25 @@ class MAIXLoader { await this.write(packet); if (await this.recvDebug()) { sendedLength += chunkLength; - this.message(this.getProgressMessage('Programming BIN', sendedLength / dataLength)); + const message = this.getProgressMessage('Programming BIN', sendedLength / dataLength); + if (i > 0) { + this.replaceMessage(-1, message); + } else { + this.message(`\n${message}`); + } address += this.ISP_FLASH_DATA_FRAME_SIZE; break; } } } + this.message('\n\n'); } dispose() { this.#port_ = null; this.#events_.reset(); this.#events_ = null; + this.#readedBuffer_ = null; } } @@ -451,10 +469,13 @@ class KFlash { #port_ = null; #loader_ = null; - constructor(port, messageCallback) { + constructor(port) { this.#port_ = port; this.#loader_ = new MAIXLoader(port); - this.#loader_.bind('message', messageCallback); + } + + bind(...args) { + return this.#loader_.bind(...args); } async enter() { @@ -462,7 +483,7 @@ class KFlash { } async write(firmware, address = 0x0, sha256Prefix = true, filename = '') { - this.#loader_.message('[INFO] Trying to Enter the ISP Mode...\n'); + this.#loader_.message('[INFO] Trying to Enter the ISP Mode...'); let retryCount = 0; while (true) { try { @@ -500,6 +521,7 @@ class KFlash { dispose() { this.#loader_.dispose(); + this.#loader_ = null; this.#port_ = null; } }