diff --git a/extensions/notebook-renderers/src/stackTraceHelper.ts b/extensions/notebook-renderers/src/stackTraceHelper.ts index 79441e8db35..3228628029d 100644 --- a/extensions/notebook-renderers/src/stackTraceHelper.ts +++ b/extensions/notebook-renderers/src/stackTraceHelper.ts @@ -22,5 +22,46 @@ export function formatStackTrace(stack: string) { return `${prefix}${num}${suffix}\n`; }); + if (isIpythonStackTrace(stack)) { + return linkifyStack(stack); + } + return cleaned; } + +function isIpythonStackTrace(stack: string) { + const cellIdentifier = /^Cell In\[\d+\], line \d+$/gm; + return cellIdentifier.test(stack); +} + +const fileRegex = /^File\s+(.+):\d+/; +const lineNumberRegex = /([ ->]*?)(\d+)(.*)/; + +function linkifyStack(stack: string) { + const lines = stack.split('\n'); + + let fileOrCell: string | undefined; + + for (const i in lines) { + + const original = lines[i]; + console.log(`linkify ${original}`); // REMOVE + if (fileRegex.test(original)) { + const fileMatch = lines[i].match(fileRegex); + fileOrCell = fileMatch![1]; + console.log(`matched file ${fileOrCell}`); // REMOVE + continue; + } else if (!fileOrCell || original.trim() === '') { + // we don't have a location, so don't linkify anything + fileOrCell = undefined; + continue; + } else if (lineNumberRegex.test(original)) { + console.log(`linkify line ${original}`); // REMOVE + lines[i] = original.replace(lineNumberRegex, (_s, prefix, num, suffix) => { + return `${prefix}${num}${suffix}`; + }); + } + } + + return lines.join('\n'); +} diff --git a/extensions/notebook-renderers/src/test/stackTraceHelper.test.ts b/extensions/notebook-renderers/src/test/stackTraceHelper.test.ts index 0fe1b861488..b40ec59e652 100644 --- a/extensions/notebook-renderers/src/test/stackTraceHelper.test.ts +++ b/extensions/notebook-renderers/src/test/stackTraceHelper.test.ts @@ -18,7 +18,7 @@ suite('StackTraceHelper', () => { assert.equal(formatStackTrace(stack), stack); }); - test('IPython cell references are linkified', () => { + test('IPython stack line numbers are linkified', () => { const stack = '---------------------------------------------------------------------------\n' + 'Exception Traceback(most recent call last)\n' + @@ -31,7 +31,21 @@ suite('StackTraceHelper', () => { '----> 2 raise Exception\n'; const formatted = formatStackTrace(stack); - assert.ok(formatted.indexOf); + assert.ok(formatted.indexOf('2') > 0, formatted); + }); + + test('IPython stack trace lines without associated location are not linkified', () => { + const stack = + '---------------------------------------------------------------------------\n' + + 'Exception Traceback(most recent call last)\n' + + 'File C:\\venvs\\myLib.py:2, in throwEx()\n' + + '\n' + + 'unkown reference' + + ' 1 import myLib\n' + // trace lines without an associated file + '----> 2 myLib.throwEx()\n'; + + const formatted = formatStackTrace(stack); + assert.ok(formatted.indexOf('